From 3739be31bbd4fa54f700d40f699f21f28af71f7c Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Mon, 3 May 2021 11:28:31 +0300 Subject: [PATCH 01/60] Init DB library --- .env | 2 +- app/config/collections2.php | 1581 ++++++++++++++++++++++++++++++ app/controllers/api/projects.php | 34 +- app/controllers/api/storage.php | 216 ++-- app/controllers/general.php | 6 + app/http.php | 4 + app/init.php | 28 + composer.json | 7 +- composer.lock | 520 ++++++---- src/Appwrite/Utopia/Response.php | 74 ++ 10 files changed, 2158 insertions(+), 314 deletions(-) create mode 100644 app/config/collections2.php diff --git a/.env b/.env index f650d0bc88..cdf12cdffc 100644 --- a/.env +++ b/.env @@ -15,7 +15,7 @@ _APP_REDIS_PORT=6379 _APP_DB_HOST=mariadb _APP_DB_PORT=3306 _APP_DB_SCHEMA=appwrite -_APP_DB_USER=user +_APP_DB_USER=root _APP_DB_PASS=password _APP_STORAGE_ANTIVIRUS=disabled _APP_STORAGE_ANTIVIRUS_HOST=clamav diff --git a/app/config/collections2.php b/app/config/collections2.php new file mode 100644 index 0000000000..7391493885 --- /dev/null +++ b/app/config/collections2.php @@ -0,0 +1,1581 @@ + 'title', +// 'type' => Database::VAR_STRING, +// 'format' => '', +// 'size' => 256, +// 'required' => true, +// 'signed' => true, +// 'array' => false, +// 'filters' => [], + +$collections = [ + 'users' => [ + '$collection' => Database::COLLECTIONS, + '$id' => 'users', + 'name' => 'Users', + 'attributes' => [ + [ + '$id' => 'name', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 256, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'email', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 1024, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'status', + 'type' => Database::VAR_INTEGER, + 'format' => '', + 'size' => 0, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'password', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 32, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'passwordUpdate', + 'type' => Database::VAR_INTEGER, + 'format' => '', + 'size' => 0, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'prefs', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 64000, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => ['json'] + + ], + [ + '$id' => 'registration', + 'type' => Database::VAR_INTEGER, + 'format' => '', + 'size' => 0, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'emailVerification', + 'type' => Database::VAR_BOOLEAN, + 'format' => '', + 'size' => 0, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'reset', + 'type' => Database::VAR_BOOLEAN, + 'format' => '', + 'size' => 0, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'sessions', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 0, + 'signed' => true, + 'required' => false, + 'array' => true, + 'filters' => ['json'], + ], + [ + '$id' => 'tokens', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 0, + 'signed' => true, + 'required' => false, + 'array' => true, + 'filters' => ['json'], + ], + [ + '$id' => 'memberships', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 0, + 'signed' => true, + 'required' => false, + 'array' => true, + 'filters' => ['json'], + + ], + ], + 'indexes' => [], + ], + // Database::SYSTEM_COLLECTION_SESSIONS => [ + // '$collection' => Database::SYSTEM_COLLECTION_COLLECTIONS, + // '$id' => Database::SYSTEM_COLLECTION_SESSIONS, + // '$permissions' => ['read' => ['*']], + // 'name' => 'Session', + // 'structure' => true, + // 'rules' => [ + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'User ID', + // 'key' => 'userId', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => null, + // 'required' => true, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Provider', + // 'key' => 'provider', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => '', + // 'required' => true, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Provider User Identifier', + // 'key' => 'providerUid', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => '', + // 'required' => false, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Provider Token', + // 'key' => 'providerToken', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => '', + // 'required' => false, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Secret', + // 'key' => 'secret', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => '', + // 'required' => true, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Expire', + // 'key' => 'expire', + // 'type' => Database::SYSTEM_VAR_TYPE_NUMERIC, + // 'default' => 0, + // 'required' => true, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'User Agent', + // 'key' => 'userAgent', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => '', + // 'required' => true, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'IP', + // 'key' => 'ip', + // 'type' => Database::SYSTEM_VAR_TYPE_IP, + // 'default' => '', + // 'required' => true, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'OS Code', + // 'key' => 'osCode', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => '', + // 'required' => false, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'OS Name', + // 'key' => 'osName', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => '', + // 'required' => false, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'OS Version', + // 'key' => 'osVersion', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => '', + // 'required' => false, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Client Type', + // 'key' => 'clientType', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => '', + // 'required' => false, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Client Code', + // 'key' => 'clientCode', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => '', + // 'required' => false, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Client Name', + // 'key' => 'clientName', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => '', + // 'required' => false, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Client Version', + // 'key' => 'clientVersion', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => '', + // 'required' => false, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Client Engine', + // 'key' => 'clientEngine', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => '', + // 'required' => false, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Client Engine Version', + // 'key' => 'clientEngineVersion', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => '', + // 'required' => false, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Device Name', + // 'key' => 'deviceName', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => '', + // 'required' => false, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Device Brand', + // 'key' => 'deviceBrand', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => '', + // 'required' => false, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Device Model', + // 'key' => 'deviceModel', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => '', + // 'required' => false, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Country Code', + // 'key' => 'countryCode', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => '', + // 'required' => false, + // 'array' => false, + // ], + // ], + // ], + // Database::SYSTEM_COLLECTION_TOKENS => [ + // '$collection' => Database::SYSTEM_COLLECTION_COLLECTIONS, + // '$id' => Database::SYSTEM_COLLECTION_TOKENS, + // '$permissions' => ['read' => ['*']], + // 'name' => 'Token', + // 'structure' => true, + // 'rules' => [ + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'User ID', + // 'key' => 'userId', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => null, + // 'required' => false, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Type', + // 'key' => 'type', + // 'type' => Database::SYSTEM_VAR_TYPE_NUMERIC, + // 'default' => null, + // 'required' => true, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Secret', + // 'key' => 'secret', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => '', + // 'required' => true, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Expire', + // 'key' => 'expire', + // 'type' => Database::SYSTEM_VAR_TYPE_NUMERIC, + // 'default' => 0, + // 'required' => true, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'User Agent', + // 'key' => 'userAgent', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => '', + // 'required' => true, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'IP', + // 'key' => 'ip', + // 'type' => Database::SYSTEM_VAR_TYPE_IP, + // 'default' => '', + // 'required' => true, + // 'array' => false, + // ], + // ], + // ], + // Database::SYSTEM_COLLECTION_MEMBERSHIPS => [ + // '$collection' => Database::SYSTEM_COLLECTION_COLLECTIONS, + // '$id' => Database::SYSTEM_COLLECTION_MEMBERSHIPS, + // '$permissions' => ['read' => ['*']], + // 'name' => 'Membership', + // 'structure' => true, + // 'rules' => [ + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Team ID', + // 'key' => 'teamId', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => null, + // 'required' => true, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'User ID', + // 'key' => 'userId', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => null, + // 'required' => true, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Roles', + // 'key' => 'roles', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => '', + // 'required' => true, + // 'array' => true, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Invited', + // 'key' => 'invited', + // 'type' => Database::SYSTEM_VAR_TYPE_NUMERIC, + // 'default' => 0, + // 'required' => false, //FIXME SHOULD BE REQUIRED + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Joined', + // 'key' => 'joined', + // 'type' => Database::SYSTEM_VAR_TYPE_NUMERIC, + // 'default' => 0, + // 'required' => true, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Confirm', + // 'key' => 'confirm', + // 'type' => Database::SYSTEM_VAR_TYPE_BOOLEAN, + // 'default' => '', + // 'required' => true, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Secret', + // 'key' => 'secret', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => '', + // 'required' => false, + // 'array' => false, + // ], + // ], + // ], + // Database::SYSTEM_COLLECTION_TEAMS => [ + // '$collection' => Database::SYSTEM_COLLECTION_COLLECTIONS, + // '$id' => Database::SYSTEM_COLLECTION_TEAMS, + // '$permissions' => ['read' => ['*']], + // 'name' => 'Team', + // 'structure' => true, + // 'rules' => [ + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Name', + // 'key' => 'name', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => '', + // 'required' => true, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Date Created', + // 'key' => 'dateCreated', + // 'type' => Database::SYSTEM_VAR_TYPE_NUMERIC, + // 'default' => '', + // 'required' => false, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Sum', + // 'key' => 'sum', + // 'type' => Database::SYSTEM_VAR_TYPE_NUMERIC, + // 'default' => '', + // 'required' => false, + // 'array' => false, + // ], + // ], + // ], + // Database::SYSTEM_COLLECTION_PROJECTS => [ + // '$collection' => Database::SYSTEM_COLLECTION_COLLECTIONS, + // '$id' => Database::SYSTEM_COLLECTION_PROJECTS, + // '$permissions' => ['read' => ['*']], + // 'name' => 'Project', + // 'structure' => true, + // 'rules' => [ + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Name', + // 'key' => 'name', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => null, + // 'required' => true, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Description', + // 'key' => 'description', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => null, + // 'required' => false, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Logo', + // 'key' => 'logo', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => '', + // 'required' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'URL', + // 'key' => 'url', + // 'type' => Database::SYSTEM_VAR_TYPE_URL, + // 'default' => '', + // 'required' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Team ID', + // 'key' => 'teamId', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => 0, + // 'required' => true, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Legal Name', + // 'key' => 'legalName', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => '', + // 'required' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Legal Country', + // 'key' => 'legalCountry', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => '', + // 'required' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Legal State', + // 'key' => 'legalState', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => '', + // 'required' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Legal City', + // 'key' => 'legalCity', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => '', + // 'required' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Legal Address', + // 'key' => 'legalAddress', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => '', + // 'required' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Legal Tax ID', + // 'key' => 'legalTaxId', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => '', + // 'required' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Max users allowed', + // 'key' => 'usersAuthLimit', + // 'type' => Database::SYSTEM_VAR_TYPE_NUMERIC, + // 'default' => 0, + // 'required' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Webhooks', + // 'key' => 'webhooks', + // 'type' => Database::SYSTEM_VAR_TYPE_DOCUMENT, + // 'default' => [], + // 'required' => false, + // 'array' => true, + // 'list' => [Database::SYSTEM_COLLECTION_WEBHOOKS], + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'API Keys', + // 'key' => 'keys', + // 'type' => Database::SYSTEM_VAR_TYPE_DOCUMENT, + // 'default' => [], + // 'required' => false, + // 'array' => true, + // 'list' => [Database::SYSTEM_COLLECTION_KEYS], + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Tasks', + // 'key' => 'tasks', + // 'type' => Database::SYSTEM_VAR_TYPE_DOCUMENT, + // 'default' => [], + // 'required' => false, + // 'array' => true, + // 'list' => [Database::SYSTEM_COLLECTION_TASKS], + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Platforms', + // 'key' => 'platforms', + // 'type' => Database::SYSTEM_VAR_TYPE_DOCUMENT, + // 'default' => [], + // 'required' => false, + // 'array' => true, + // 'list' => [Database::SYSTEM_COLLECTION_PLATFORMS], + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Domains', + // 'key' => 'domains', + // 'type' => Database::SYSTEM_VAR_TYPE_DOCUMENT, + // 'default' => [], + // 'required' => false, + // 'array' => true, + // 'list' => [Database::SYSTEM_COLLECTION_DOMAINS], + // ], + // ], + // ], + // Database::SYSTEM_COLLECTION_WEBHOOKS => [ + // '$collection' => Database::SYSTEM_COLLECTION_COLLECTIONS, + // '$id' => Database::SYSTEM_COLLECTION_WEBHOOKS, + // '$permissions' => ['read' => ['*']], + // 'name' => 'Webhook', + // 'structure' => true, + // 'rules' => [ + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Name', + // 'key' => 'name', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => null, + // 'required' => true, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Events', + // 'key' => 'events', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => null, + // 'required' => false, + // 'array' => true, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'URL', + // 'key' => 'url', + // 'type' => Database::SYSTEM_VAR_TYPE_URL, + // 'default' => '', + // 'required' => false, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Security', + // 'key' => 'security', + // 'type' => Database::SYSTEM_VAR_TYPE_BOOLEAN, + // 'default' => '', + // 'required' => false, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'HTTP User', + // 'key' => 'httpUser', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => '', + // 'required' => false, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'HTTP Password', + // 'key' => 'httpPass', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => '', + // 'required' => false, + // 'array' => false, + // 'filter' => ['encrypt'], + // ], + // ], + // ], + // Database::SYSTEM_COLLECTION_KEYS => [ + // '$collection' => Database::SYSTEM_COLLECTION_COLLECTIONS, + // '$id' => Database::SYSTEM_COLLECTION_KEYS, + // '$permissions' => ['read' => ['*']], + // 'name' => 'Key', + // 'structure' => true, + // 'rules' => [ + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Name', + // 'key' => 'name', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => null, + // 'required' => true, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Scopes', + // 'key' => 'scopes', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => null, + // 'required' => false, + // 'array' => true, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Secret', + // 'key' => 'secret', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => '', + // 'required' => false, + // 'filter' => ['encrypt'], + // ], + // ], + // ], + // Database::SYSTEM_COLLECTION_TASKS => [ + // '$collection' => Database::SYSTEM_COLLECTION_COLLECTIONS, + // '$id' => Database::SYSTEM_COLLECTION_TASKS, + // '$permissions' => ['read' => ['*']], + // 'name' => 'Task', + // 'structure' => true, + // 'rules' => [ + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Name', + // 'key' => 'name', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => null, + // 'required' => true, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Status', + // 'key' => 'status', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => '', + // 'required' => false, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Updated', + // 'key' => 'updated', + // 'type' => Database::SYSTEM_VAR_TYPE_NUMERIC, + // 'default' => '', + // 'required' => false, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Schedule', + // 'key' => 'schedule', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => '', + // 'required' => false, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Previous', + // 'key' => 'previous', + // 'type' => Database::SYSTEM_VAR_TYPE_NUMERIC, + // 'default' => '', + // 'required' => false, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Next', + // 'key' => 'next', + // 'type' => Database::SYSTEM_VAR_TYPE_NUMERIC, + // 'default' => '', + // 'required' => false, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Duration', + // 'key' => 'duration', + // 'type' => Database::SYSTEM_VAR_TYPE_NUMERIC, + // 'default' => '', + // 'required' => false, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Delay', + // 'key' => 'delay', + // 'type' => Database::SYSTEM_VAR_TYPE_NUMERIC, + // 'default' => '', + // 'required' => false, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Security', + // 'key' => 'security', + // 'type' => Database::SYSTEM_VAR_TYPE_BOOLEAN, + // 'default' => '', + // 'required' => false, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'HTTP Method', + // 'key' => 'httpMethod', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => '', + // 'required' => false, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'HTTP URL', + // 'key' => 'httpUrl', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => '', + // 'required' => false, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'HTTP Headers', + // 'key' => 'httpHeaders', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => null, + // 'required' => false, + // 'array' => true, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'HTTP User', + // 'key' => 'httpUser', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => '', + // 'required' => false, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'HTTP Password', + // 'key' => 'httpPass', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => '', + // 'required' => false, + // 'array' => false, + // 'filter' => ['encrypt'], + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Log', + // 'key' => 'log', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => '', + // 'required' => false, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Failures', + // 'key' => 'failures', + // 'type' => Database::SYSTEM_VAR_TYPE_NUMERIC, + // 'default' => '', + // 'required' => false, + // 'array' => false, + // ], + // ], + // ], + // Database::SYSTEM_COLLECTION_PLATFORMS => [ + // '$collection' => Database::SYSTEM_COLLECTION_COLLECTIONS, + // '$id' => Database::SYSTEM_COLLECTION_PLATFORMS, + // '$permissions' => ['read' => ['*']], + // 'name' => 'Platform', + // 'structure' => true, + // 'rules' => [ + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Type', + // 'key' => 'type', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => null, + // 'required' => true, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Name', + // 'key' => 'name', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => null, + // 'required' => true, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Date Created', + // 'key' => 'dateCreated', + // 'type' => Database::SYSTEM_VAR_TYPE_NUMERIC, + // 'default' => 0, + // 'required' => false, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Date Updated', + // 'key' => 'dateUpdated', + // 'type' => Database::SYSTEM_VAR_TYPE_NUMERIC, + // 'default' => 0, + // 'required' => false, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Key', + // 'key' => 'key', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => '', + // 'required' => false, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Store', + // 'key' => 'store', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => '', + // 'required' => false, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Hostname', + // 'key' => 'hostname', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => '', + // 'required' => false, + // 'array' => false, + // ], + // ], + // ], + // Database::SYSTEM_COLLECTION_DOMAINS => [ + // '$collection' => Database::SYSTEM_COLLECTION_COLLECTIONS, + // '$id' => Database::SYSTEM_COLLECTION_DOMAINS, + // '$permissions' => ['read' => ['*']], + // 'name' => 'Domains', + // 'structure' => true, + // 'rules' => [ + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Domain', + // 'key' => 'domain', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => null, + // 'required' => true, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Updated', + // 'key' => 'updated', + // 'type' => Database::SYSTEM_VAR_TYPE_NUMERIC, + // 'default' => 0, + // 'required' => false, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Top Level Domain', + // 'key' => 'tld', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => '', + // 'required' => false, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Registerable Domain', + // 'key' => 'registerable', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => '', + // 'required' => false, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Verification', + // 'key' => 'verification', + // 'type' => Database::SYSTEM_VAR_TYPE_BOOLEAN, + // 'default' => false, + // 'required' => true, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Certificate ID', + // 'key' => 'certificateId', + // 'type' => Database::SYSTEM_VAR_TYPE_KEY, + // 'default' => '', + // 'required' => false, + // 'array' => false, + // ], + // ], + // ], + // Database::SYSTEM_COLLECTION_CERTIFICATES => [ + // '$collection' => Database::SYSTEM_COLLECTION_COLLECTIONS, + // '$id' => Database::SYSTEM_COLLECTION_CERTIFICATES, + // '$permissions' => ['read' => ['*']], + // 'name' => 'Certificates', + // 'structure' => true, + // 'rules' => [ + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Domain', + // 'key' => 'domain', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => null, + // 'required' => true, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Issue Date', + // 'key' => 'issueDate', + // 'type' => Database::SYSTEM_VAR_TYPE_NUMERIC, + // 'default' => 0, + // 'required' => false, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Renew Date', + // 'key' => 'renewDate', + // 'type' => Database::SYSTEM_VAR_TYPE_NUMERIC, + // 'default' => 0, + // 'required' => false, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Attempts', + // 'key' => 'attempts', + // 'type' => Database::SYSTEM_VAR_TYPE_NUMERIC, + // 'default' => 0, + // 'required' => false, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Log', + // 'key' => 'log', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => '', + // 'required' => false, + // 'array' => false, + // ], + // ], + // ], + 'files' => [ + '$collection' => Database::COLLECTIONS, + '$id' => 'files', + 'name' => 'Files', + 'attributes' => [ + [ + '$id' => 'dateCreated', + 'type' => Database::VAR_INTEGER, + 'format' => '', + 'size' => 0, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + [ + 'array' => false, + '$id' => 'bucketId', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + [ + 'array' => false, + '$id' => 'name', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 2048, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'path', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 2048, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'signature', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 2048, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'mimeType', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 127, // https://tools.ietf.org/html/rfc4288#section-4.2 + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'sizeOriginal', + 'type' => Database::VAR_INTEGER, + 'format' => '', + 'size' => 0, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'sizeActual', + 'type' => Database::VAR_INTEGER, + 'format' => '', + 'size' => 0, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'algorithm', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 255, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'comment', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 2048, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'openSSLVersion', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 64, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'openSSLCipher', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 64, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'openSSLTag', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 2048, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'openSSLIV', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 2048, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + ], + 'indexes' => [], + ], + // Database::SYSTEM_COLLECTION_FUNCTIONS => [ + // '$collection' => Database::SYSTEM_COLLECTION_COLLECTIONS, + // '$id' => Database::SYSTEM_COLLECTION_FUNCTIONS, + // '$permissions' => ['read' => ['*']], + // 'name' => 'Functions', + // 'structure' => true, + // 'rules' => [ + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Date Created', + // 'key' => 'dateCreated', + // 'type' => Database::SYSTEM_VAR_TYPE_NUMERIC, + // 'default' => 0, + // 'required' => false, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Date Updated', + // 'key' => 'dateUpdated', + // 'type' => Database::SYSTEM_VAR_TYPE_NUMERIC, + // 'default' => 0, + // 'required' => false, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Status', + // 'key' => 'status', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => '', + // 'required' => false, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Name', + // 'key' => 'name', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => '', + // 'required' => false, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Env', + // 'key' => 'env', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => '', + // 'required' => false, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Tag', + // 'key' => 'tag', + // 'type' => Database::SYSTEM_VAR_TYPE_KEY, + // 'default' => '', + // 'required' => false, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Vars', + // 'key' => 'vars', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => '', + // 'required' => false, + // 'array' => false, + // 'filter' => ['json', 'encrypt'] + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Events', + // 'key' => 'events', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => '', + // 'required' => false, + // 'array' => true, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Schedule', + // 'key' => 'schedule', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => '', + // 'required' => false, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Schedule Previous Run', + // 'key' => 'schedulePrevious', + // 'type' => Database::SYSTEM_VAR_TYPE_NUMERIC, + // 'default' => '', + // 'required' => false, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Schedule Next Run', + // 'key' => 'scheduleNext', + // 'type' => Database::SYSTEM_VAR_TYPE_NUMERIC, + // 'default' => '', + // 'required' => false, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Timeout', + // 'key' => 'timeout', + // 'type' => Database::SYSTEM_VAR_TYPE_NUMERIC, + // 'default' => '', + // 'required' => false, + // 'array' => false, + // ], + // ], + // ], + // Database::SYSTEM_COLLECTION_TAGS => [ + // '$collection' => Database::SYSTEM_COLLECTION_COLLECTIONS, + // '$id' => Database::SYSTEM_COLLECTION_TAGS, + // '$permissions' => ['read' => ['*']], + // 'name' => 'Tags', + // 'structure' => true, + // 'rules' => [ + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Date Created', + // 'key' => 'dateCreated', + // 'type' => Database::SYSTEM_VAR_TYPE_NUMERIC, + // 'default' => 0, + // 'required' => false, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Function ID', + // 'key' => 'functionId', + // 'type' => Database::SYSTEM_VAR_TYPE_KEY, + // 'default' => '', + // 'required' => false, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Command', + // 'key' => 'command', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => '', + // 'required' => false, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Code Path', + // 'key' => 'path', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => '', + // 'required' => false, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Code Size', + // 'key' => 'size', + // 'type' => Database::SYSTEM_VAR_TYPE_NUMERIC, + // 'default' => '', + // 'required' => false, + // 'array' => false, + // ], + // ], + // ], + // Database::SYSTEM_COLLECTION_EXECUTIONS => [ + // '$collection' => Database::SYSTEM_COLLECTION_COLLECTIONS, + // '$id' => Database::SYSTEM_COLLECTION_EXECUTIONS, + // '$permissions' => ['read' => ['*']], + // 'name' => 'Executions', + // 'structure' => true, + // 'rules' => [ + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Date Created', + // 'key' => 'dateCreated', + // 'type' => Database::SYSTEM_VAR_TYPE_NUMERIC, + // 'default' => 0, + // 'required' => false, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Function ID', + // 'key' => 'functionId', + // 'type' => Database::SYSTEM_VAR_TYPE_KEY, + // 'default' => '', + // 'required' => false, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Tag ID', + // 'key' => 'tagId', + // 'type' => Database::SYSTEM_VAR_TYPE_KEY, + // 'default' => '', + // 'required' => false, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Trigger', + // 'key' => 'trigger', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => '', + // 'required' => false, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Status', + // 'key' => 'status', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => '', + // 'required' => false, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Exit Code', + // 'key' => 'exitCode', + // 'type' => Database::SYSTEM_VAR_TYPE_NUMERIC, + // 'default' => '', + // 'required' => false, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Stdout', + // 'key' => 'stdout', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => '', + // 'required' => false, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Stderr', + // 'key' => 'stderr', + // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + // 'default' => '', + // 'required' => false, + // 'array' => false, + // ], + // [ + // '$collection' => Database::SYSTEM_COLLECTION_RULES, + // 'label' => 'Time', + // 'key' => 'time', + // 'type' => Database::SYSTEM_VAR_TYPE_NUMERIC, + // 'default' => '', + // 'required' => false, + // 'array' => false, + // ], + // ], + // ], + // Database::SYSTEM_COLLECTION_RESERVED => [ + // '$collection' => Database::SYSTEM_COLLECTION_COLLECTIONS, + // '$id' => Database::SYSTEM_COLLECTION_RESERVED, + // '$permissions' => ['read' => ['*']], + // 'name' => 'Reserved', + // 'structure' => true, + // ], +]; + +/* + * Add enabled OAuth2 providers to default data rules + */ +// foreach ($providers as $index => $provider) { +// if (!$provider['enabled']) { +// continue; +// } + +// $collections[Database::SYSTEM_COLLECTION_PROJECTS]['rules'][] = [ +// '$collection' => Database::SYSTEM_COLLECTION_RULES, +// 'label' => 'OAuth2 '.\ucfirst($index).' ID', +// 'key' => 'usersOauth2'.\ucfirst($index).'Appid', +// 'type' => Database::SYSTEM_VAR_TYPE_TEXT, +// 'default' => '', +// 'required' => false, +// 'array' => false, +// ]; + +// $collections[Database::SYSTEM_COLLECTION_PROJECTS]['rules'][] = [ +// '$collection' => Database::SYSTEM_COLLECTION_RULES, +// 'label' => 'OAuth2 '.\ucfirst($index).' Secret', +// 'key' => 'usersOauth2'.\ucfirst($index).'Secret', +// 'type' => Database::SYSTEM_VAR_TYPE_TEXT, +// 'default' => '', +// 'required' => false, +// 'array' => false, +// 'filter' => ['encrypt'], +// ]; +// } + +// foreach ($auth as $index => $method) { +// $collections[Database::SYSTEM_COLLECTION_PROJECTS]['rules'][] = [ +// '$collection' => Database::SYSTEM_COLLECTION_RULES, +// 'label' => $method['name'] || '', +// 'key' => $method['key'] || '', +// 'type' => Database::SYSTEM_VAR_TYPE_BOOLEAN, +// 'default' => true, +// 'required' => false, +// ]; +// } + +return $collections; \ No newline at end of file diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index 0b9eb64ff0..a3aeed15f9 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -45,10 +45,14 @@ App::post('/v1/projects') ->inject('response') ->inject('consoleDB') ->inject('projectDB') - ->action(function ($name, $teamId, $description, $logo, $url, $legalName, $legalCountry, $legalState, $legalCity, $legalAddress, $legalTaxId, $response, $consoleDB, $projectDB) { + ->inject('dbForInternal') + ->inject('dbForExternal') + ->action(function ($name, $teamId, $description, $logo, $url, $legalName, $legalCountry, $legalState, $legalCity, $legalAddress, $legalTaxId, $response, $consoleDB, $projectDB, $dbForInternal, $dbForExternal) { /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $consoleDB */ /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Database $dbForInternal */ + /** @var Utopia\Database\Database $dbForExternal */ $team = $projectDB->getDocument($teamId); @@ -88,6 +92,34 @@ App::post('/v1/projects') $consoleDB->createNamespace($project->getId()); + $collections = Config::getParam('collections2', []); /** @var array $collections */ + + $dbForInternal->setNamespace('project_internal_'.$project->getId()); + $dbForInternal->create(); + $dbForExternal->setNamespace('project_external_'.$project->getId()); + $dbForExternal->create(); + + foreach ($collections as $key => $collection) { + $dbForInternal->createCollection($key); + + foreach ($collection['attributes'] as $i => $attribute) { + $dbForInternal->createAttribute( + $key, + $attribute['$id'], + $attribute['type'], + $attribute['size'], + $attribute['required'], + $attribute['signed'], + $attribute['array'], + $attribute['filters'], + ); + } + + foreach ($collection['indexes'] as $i => $index) { + + } + } + $response ->setStatusCode(Response::STATUS_CODE_CREATED) ->dynamic($project, Response::MODEL_PROJECT) diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index 994af027d9..ff9fe94675 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -10,8 +10,7 @@ use Utopia\Validator\HexColor; use Utopia\Cache\Cache; use Utopia\Cache\Adapter\Filesystem; use Appwrite\ClamAV\Network; -use Appwrite\Database\Database; -use Appwrite\Database\Document; +use Utopia\Database\Document; use Appwrite\Database\Validator\UID; use Utopia\Storage\Storage; use Utopia\Storage\Validator\File; @@ -42,14 +41,14 @@ App::post('/v1/storage/files') ->param('write', null, new ArrayList(new Text(64)), 'An array of strings with write permissions. By default only the current user is granted with write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.', true) ->inject('request') ->inject('response') - ->inject('projectDB') + ->inject('dbForInternal') ->inject('user') ->inject('audits') ->inject('usage') - ->action(function ($file, $read, $write, $request, $response, $projectDB, $user, $audits, $usage) { + ->action(function ($file, $read, $write, $request, $response, $dbForInternal, $user, $audits, $usage) { /** @var Utopia\Swoole\Request $request */ /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Database $dbForInternal */ /** @var Appwrite\Database\Document $user */ /** @var Appwrite\Event\Event $audits */ /** @var Appwrite\Event\Event $usage */ @@ -121,14 +120,11 @@ App::post('/v1/storage/files') $sizeActual = $device->getFileSize($path); - $file = $projectDB->createDocument([ - '$collection' => Database::SYSTEM_COLLECTION_FILES, - '$permissions' => [ - 'read' => (is_null($read) && !$user->isEmpty()) ? ['user:'.$user->getId()] : $read ?? [], // By default set read permissions for user - 'write' => (is_null($write) && !$user->isEmpty()) ? ['user:'.$user->getId()] : $write ?? [], // By default set write permissions for user - ], + $file = $dbForInternal->createDocument('files', new Document([ + '$read' => (is_null($read) && !$user->isEmpty()) ? ['user:'.$user->getId()] : $read ?? [], // By default set read permissions for user + '$write' => (is_null($write) && !$user->isEmpty()) ? ['user:'.$user->getId()] : $write ?? [], // By default set write permissions for user 'dateCreated' => \time(), - 'folderId' => '', + 'bucketId' => '', 'name' => $file['name'], 'path' => $path, 'signature' => $device->getFileHash($path), @@ -136,13 +132,12 @@ App::post('/v1/storage/files') 'sizeOriginal' => $size, 'sizeActual' => $sizeActual, 'algorithm' => $compressor->getName(), - 'token' => \bin2hex(\random_bytes(64)), 'comment' => '', - 'fileOpenSSLVersion' => '1', - 'fileOpenSSLCipher' => OpenSSL::CIPHER_AES_128_GCM, - 'fileOpenSSLTag' => \bin2hex($tag), - 'fileOpenSSLIV' => \bin2hex($iv), - ]); + 'openSSLVersion' => '1', + 'openSSLCipher' => OpenSSL::CIPHER_AES_128_GCM, + 'openSSLTag' => \bin2hex($tag), + 'openSSLIV' => \bin2hex($iv), + ])); if (false === $file) { throw new Exception('Failed saving file to DB', 500); @@ -159,7 +154,7 @@ App::post('/v1/storage/files') $response ->setStatusCode(Response::STATUS_CODE_CREATED) - ->dynamic($file, Response::MODEL_FILE) + ->dynamic2($file, Response::MODEL_FILE) ; }); @@ -179,23 +174,15 @@ App::get('/v1/storage/files') ->param('offset', 0, new Range(0, 2000), 'Results offset. The default value is 0. Use this param to manage pagination.', true) ->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true) ->inject('response') - ->inject('projectDB') - ->action(function ($search, $limit, $offset, $orderType, $response, $projectDB) { + ->inject('dbForInternal') + ->action(function ($search, $limit, $offset, $orderType, $response, $dbForInternal) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Database $dbForInternal */ - $results = $projectDB->getCollection([ - 'limit' => $limit, - 'offset' => $offset, - 'orderType' => $orderType, - 'search' => $search, - 'filters' => [ - '$collection='.Database::SYSTEM_COLLECTION_FILES, - ], - ]); + $results = $dbForInternal->find('files', [], $limit, $offset); - $response->dynamic(new Document([ - 'sum' => $projectDB->getSum(), + $response->dynamic2(new Document([ + 'sum' => $limit, 'files' => $results ]), Response::MODEL_FILE_LIST); }); @@ -213,18 +200,18 @@ App::get('/v1/storage/files/:fileId') ->label('sdk.response.model', Response::MODEL_FILE) ->param('fileId', '', new UID(), 'File unique ID.') ->inject('response') - ->inject('projectDB') - ->action(function ($fileId, $response, $projectDB) { + ->inject('dbForInternal') + ->action(function ($fileId, $response, $dbForInternal) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Database $dbForInternal */ - $file = $projectDB->getDocument($fileId); + $file = $dbForInternal->getDocument('files', $fileId); - if (empty($file->getId()) || Database::SYSTEM_COLLECTION_FILES != $file->getCollection()) { + if (empty($file->getId())) { throw new Exception('File not found', 404); } - $response->dynamic($file, Response::MODEL_FILE); + $response->dynamic2($file, Response::MODEL_FILE); }); App::get('/v1/storage/files/:fileId/preview') @@ -252,12 +239,12 @@ App::get('/v1/storage/files/:fileId/preview') ->inject('request') ->inject('response') ->inject('project') - ->inject('projectDB') - ->action(function ($fileId, $width, $height, $quality, $borderWidth, $borderColor, $borderRadius, $opacity, $rotation, $background, $output, $request, $response, $project, $projectDB) { + ->inject('dbForInternal') + ->action(function ($fileId, $width, $height, $quality, $borderWidth, $borderColor, $borderRadius, $opacity, $rotation, $background, $output, $request, $response, $project, $dbForInternal) { /** @var Utopia\Swoole\Request $request */ /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Document $project */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Document $project */ + /** @var Utopia\Database\Database $dbForInternal */ $storage = 'files'; @@ -280,16 +267,16 @@ App::get('/v1/storage/files/:fileId/preview') $date = \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)).' GMT'; // 45 days cache $key = \md5($fileId.$width.$height.$quality.$borderWidth.$borderColor.$borderRadius.$opacity.$rotation.$background.$storage.$output); - $file = $projectDB->getDocument($fileId); + $file = $dbForInternal->getDocument('files', $fileId); - if (empty($file->getId()) || Database::SYSTEM_COLLECTION_FILES != $file->getCollection()) { + if (empty($file->getId())) { throw new Exception('File not found', 404); } $path = $file->getAttribute('path'); $type = \strtolower(\pathinfo($path, PATHINFO_EXTENSION)); $algorithm = $file->getAttribute('algorithm'); - $cipher = $file->getAttribute('fileOpenSSLCipher'); + $cipher = $file->getAttribute('openSSLCipher'); $mime = $file->getAttribute('mimeType'); if (!\in_array($mime, $inputs)) { @@ -327,11 +314,11 @@ App::get('/v1/storage/files/:fileId/preview') if (!empty($cipher)) { // Decrypt $source = OpenSSL::decrypt( $source, - $file->getAttribute('fileOpenSSLCipher'), - App::getEnv('_APP_OPENSSL_KEY_V'.$file->getAttribute('fileOpenSSLVersion')), + $file->getAttribute('openSSLCipher'), + App::getEnv('_APP_OPENSSL_KEY_V'.$file->getAttribute('openSSLVersion')), 0, - \hex2bin($file->getAttribute('fileOpenSSLIV')), - \hex2bin($file->getAttribute('fileOpenSSLTag')) + \hex2bin($file->getAttribute('openSSLIV')), + \hex2bin($file->getAttribute('openSSLTag')) ); } @@ -393,14 +380,14 @@ App::get('/v1/storage/files/:fileId/download') ->label('sdk.methodType', 'location') ->param('fileId', '', new UID(), 'File unique ID.') ->inject('response') - ->inject('projectDB') - ->action(function ($fileId, $response, $projectDB) { + ->inject('dbForInternal') + ->action(function ($fileId, $response, $dbForInternal) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Database $dbForInternal */ - $file = $projectDB->getDocument($fileId); + $file = $dbForInternal->getDocument('files', $fileId); - if (empty($file->getId()) || Database::SYSTEM_COLLECTION_FILES != $file->getCollection()) { + if (empty($file->getId())) { throw new Exception('File not found', 404); } @@ -415,14 +402,14 @@ App::get('/v1/storage/files/:fileId/download') $source = $device->read($path); - if (!empty($file->getAttribute('fileOpenSSLCipher'))) { // Decrypt + if (!empty($file->getAttribute('openSSLCipher'))) { // Decrypt $source = OpenSSL::decrypt( $source, - $file->getAttribute('fileOpenSSLCipher'), - App::getEnv('_APP_OPENSSL_KEY_V'.$file->getAttribute('fileOpenSSLVersion')), + $file->getAttribute('openSSLCipher'), + App::getEnv('_APP_OPENSSL_KEY_V'.$file->getAttribute('openSSLVersion')), 0, - \hex2bin($file->getAttribute('fileOpenSSLIV')), - \hex2bin($file->getAttribute('fileOpenSSLTag')) + \hex2bin($file->getAttribute('openSSLIV')), + \hex2bin($file->getAttribute('openSSLTag')) ); } @@ -451,15 +438,15 @@ App::get('/v1/storage/files/:fileId/view') ->label('sdk.methodType', 'location') ->param('fileId', '', new UID(), 'File unique ID.') ->inject('response') - ->inject('projectDB') + ->inject('dbForInternal') ->action(function ($fileId, $response, $projectDB) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Database $dbForInternal */ - $file = $projectDB->getDocument($fileId); + $file = $projectDB->getDocument('files', $fileId); $mimes = Config::getParam('storage-mimes'); - if (empty($file->getId()) || Database::SYSTEM_COLLECTION_FILES != $file->getCollection()) { + if (empty($file->getId())) { throw new Exception('File not found', 404); } @@ -480,14 +467,14 @@ App::get('/v1/storage/files/:fileId/view') $source = $device->read($path); - if (!empty($file->getAttribute('fileOpenSSLCipher'))) { // Decrypt + if (!empty($file->getAttribute('openSSLCipher'))) { // Decrypt $source = OpenSSL::decrypt( $source, - $file->getAttribute('fileOpenSSLCipher'), - App::getEnv('_APP_OPENSSL_KEY_V'.$file->getAttribute('fileOpenSSLVersion')), + $file->getAttribute('openSSLCipher'), + App::getEnv('_APP_OPENSSL_KEY_V'.$file->getAttribute('openSSLVersion')), 0, - \hex2bin($file->getAttribute('fileOpenSSLIV')), - \hex2bin($file->getAttribute('fileOpenSSLTag')) + \hex2bin($file->getAttribute('openSSLIV')), + \hex2bin($file->getAttribute('openSSLTag')) ); } @@ -522,26 +509,24 @@ App::put('/v1/storage/files/:fileId') ->param('read', [], new ArrayList(new Text(64)), 'An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.') ->param('write', [], new ArrayList(new Text(64)), 'An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.') ->inject('response') - ->inject('projectDB') + ->inject('dbForInternal') ->inject('audits') - ->action(function ($fileId, $read, $write, $response, $projectDB, $audits) { + ->action(function ($fileId, $read, $write, $response, $dbForInternal, $audits) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Database $dbForInternal */ /** @var Appwrite\Event\Event $audits */ - $file = $projectDB->getDocument($fileId); + $file = $dbForInternal->getDocument('files', $fileId); - if (empty($file->getId()) || Database::SYSTEM_COLLECTION_FILES != $file->getCollection()) { + if (empty($file->getId())) { throw new Exception('File not found', 404); } - $file = $projectDB->updateDocument(\array_merge($file->getArrayCopy(), [ - '$permissions' => [ - 'read' => $read, - 'write' => $write, - ], - 'folderId' => '', - ])); + $file = $dbForInternal->updateDocument('files', $fileId, new Document(\array_merge($file->getArrayCopy(), [ + '$read' => $read, + '$write' => $write, + 'bucketId' => '', + ]))); if (false === $file) { throw new Exception('Failed saving file to DB', 500); @@ -552,7 +537,7 @@ App::put('/v1/storage/files/:fileId') ->setParam('resource', 'storage/files/'.$file->getId()) ; - $response->dynamic($file, Response::MODEL_FILE); + $response->dynamic2($file, Response::MODEL_FILE); }); App::delete('/v1/storage/files/:fileId') @@ -568,27 +553,27 @@ App::delete('/v1/storage/files/:fileId') ->label('sdk.response.model', Response::MODEL_NONE) ->param('fileId', '', new UID(), 'File unique ID.') ->inject('response') - ->inject('projectDB') + ->inject('dbForInternal') ->inject('events') ->inject('audits') ->inject('usage') - ->action(function ($fileId, $response, $projectDB, $events, $audits, $usage) { + ->action(function ($fileId, $response, $dbForInternal, $events, $audits, $usage) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Database $dbForInternal */ /** @var Appwrite\Event\Event $events */ /** @var Appwrite\Event\Event $audits */ /** @var Appwrite\Event\Event $usage */ - $file = $projectDB->getDocument($fileId); + $file = $dbForInternal->getDocument('files', $fileId); - if (empty($file->getId()) || Database::SYSTEM_COLLECTION_FILES != $file->getCollection()) { + if (empty($file->getId())) { throw new Exception('File not found', 404); } $device = Storage::getDevice('files'); if ($device->delete($file->getAttribute('path', ''))) { - if (!$projectDB->deleteDocument($fileId)) { + if (!$dbForInternal->deleteDocument('files', $fileId)) { throw new Exception('Failed to remove file from DB', 500); } } @@ -603,59 +588,8 @@ App::delete('/v1/storage/files/:fileId') ; $events - ->setParam('eventData', $response->output($file, Response::MODEL_FILE)) + ->setParam('eventData', $response->output2($file, Response::MODEL_FILE)) ; $response->noContent(); - }); - -// App::get('/v1/storage/files/:fileId/scan') -// ->desc('Scan Storage') -// ->groups(['api', 'storage']) -// ->label('scope', 'god') -// ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT]) -// ->label('sdk.namespace', 'storage') -// ->label('sdk.method', 'getFileScan') -// ->label('sdk.hide', true) -// ->param('fileId', '', new UID(), 'File unique ID.') -// ->param('storage', 'files', new WhiteList(['files']);}) -// ->action( -// function ($fileId, $storage) use ($response, $request, $projectDB) { -// $file = $projectDB->getDocument($fileId); - -// if (empty($file->getId()) || Database::SYSTEM_COLLECTION_FILES != $file->getCollection()) { -// throw new Exception('File not found', 404); -// } - -// $path = $file->getAttribute('path', ''); - -// if (!file_exists($path)) { -// throw new Exception('File not found in '.$path, 404); -// } - -// $compressor = new GZIP(); -// $device = Storage::getDevice($storage); - -// $source = $device->read($path); - -// if (!empty($file->getAttribute('fileOpenSSLCipher'))) { // Decrypt -// $source = OpenSSL::decrypt( -// $source, -// $file->getAttribute('fileOpenSSLCipher'), -// App::getEnv('_APP_OPENSSL_KEY_V'.$file->getAttribute('fileOpenSSLVersion')), -// 0, -// hex2bin($file->getAttribute('fileOpenSSLIV')), -// hex2bin($file->getAttribute('fileOpenSSLTag')) -// ); -// } - -// $source = $compressor->decompress($source); - -// $antiVirus = new Network('clamav', 3310); - -// //var_dump($antiVirus->ping()); -// //var_dump($antiVirus->version()); -// //var_dump($antiVirus->fileScan('/storage/uploads/app-1/5/9/f/e/59fecaed49645.pdf')); - -// } -// ); + }); \ No newline at end of file diff --git a/app/controllers/general.php b/app/controllers/general.php index 1f7d4ebaeb..47950c79c8 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -18,6 +18,7 @@ use Utopia\Storage\Device\Local; use Utopia\Storage\Storage; use Appwrite\Utopia\Response\Filters\V06; use Utopia\CLI\Console; +use Utopia\Database\Validator\Authorization as Authorization2; Config::setParam('domainVerification', false); Config::setParam('cookieDomain', 'localhost'); @@ -189,21 +190,26 @@ App::init(function ($utopia, $request, $response, $console, $project, $user, $lo $scopes = \array_merge($roles[$role]['scopes'], $key->getAttribute('scopes', [])); Authorization::setDefaultStatus(false); // Cancel security segmentation for API keys. + Authorization2::setDefaultStatus(false); // Cancel security segmentation for API keys. } } if ($user->getId()) { Authorization::setRole('user:'.$user->getId()); + Authorization2::setRole('user:'.$user->getId()); } Authorization::setRole('role:'.$role); + Authorization2::setRole('role:'.$role); \array_map(function ($node) { if (isset($node['teamId']) && isset($node['roles'])) { Authorization::setRole('team:'.$node['teamId']); + Authorization2::setRole('team:'.$node['teamId']); foreach ($node['roles'] as $nodeRole) { // Set all team roles Authorization::setRole('team:'.$node['teamId'].'/'.$nodeRole); + Authorization2::setRole('team:'.$node['teamId'].'/'.$nodeRole); } } }, $user->getAttribute('memberships', [])); diff --git a/app/http.php b/app/http.php index d144c2e54a..23679a90bb 100644 --- a/app/http.php +++ b/app/http.php @@ -12,6 +12,7 @@ use Swoole\Http\Request as SwooleRequest; use Swoole\Http\Response as SwooleResponse; use Utopia\App; use Utopia\CLI\Console; +use Utopia\Database\Validator\Authorization as Authorization2; // xdebug_start_trace('/tmp/trace'); @@ -100,6 +101,9 @@ $http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swo Authorization::cleanRoles(); Authorization::setRole('*'); + Authorization2::cleanRoles(); + Authorization2::setRole('*'); + $app->run($request, $response); } catch (\Throwable $th) { Console::error('[Error] Type: '.get_class($th)); diff --git a/app/init.php b/app/init.php index bc5e37d28a..1af0027a8d 100644 --- a/app/init.php +++ b/app/init.php @@ -30,6 +30,11 @@ use Utopia\Registry\Registry; use MaxMind\Db\Reader; use PHPMailer\PHPMailer\PHPMailer; use PDO as PDONative; +use Utopia\Cache\Adapter\None; +use Utopia\Cache\Cache; +use Utopia\Database\Adapter\MariaDB; +use Utopia\Database\Database as DatabaseDatabase; +use Utopia\Database\Validator\Authorization as Authorization2; const APP_NAME = 'Appwrite'; const APP_DOMAIN = 'appwrite.io'; @@ -79,6 +84,7 @@ Config::load('auth', __DIR__.'/config/auth.php'); Config::load('providers', __DIR__.'/config/providers.php'); Config::load('platforms', __DIR__.'/config/platforms.php'); Config::load('collections', __DIR__.'/config/collections.php'); +Config::load('collections2', __DIR__.'/config/collections2.php'); Config::load('runtimes', __DIR__.'/config/runtimes.php'); Config::load('roles', __DIR__.'/config/roles.php'); // User roles and scopes Config::load('scopes', __DIR__.'/config/scopes.php'); // User roles and scopes @@ -388,6 +394,7 @@ App::setResource('user', function($mode, $project, $console, $request, $response /** @var bool $mode */ Authorization::setDefaultStatus(true); + Authorization2::setDefaultStatus(true); Auth::setCookieName('a_session_'.$project->getId()); @@ -432,6 +439,7 @@ App::setResource('user', function($mode, $project, $console, $request, $response if (APP_MODE_ADMIN === $mode) { if (!empty($user->search('teamId', $project->getAttribute('teamId'), $user->getAttribute('memberships')))) { Authorization::setDefaultStatus(false); // Cancel security segmentation for admin users. + Authorization2::setDefaultStatus(false); // Cancel security segmentation for admin users. } else { $user = new Document(['$id' => '', '$collection' => Database::SYSTEM_COLLECTION_USERS]); } @@ -468,11 +476,13 @@ App::setResource('project', function($consoleDB, $request) { /** @var Appwrite\Database\Database $consoleDB */ Authorization::disable(); + Authorization2::disable(); $project = $consoleDB->getDocument($request->getParam('project', $request->getHeader('x-appwrite-project', ''))); Authorization::reset(); + Authorization2::reset(); return $project; }, ['consoleDB', 'request']); @@ -499,6 +509,24 @@ App::setResource('projectDB', function($register, $project) { return $projectDB; }, ['register', 'project']); +App::setResource('dbForInternal', function($register, $project) { + $cache = new Cache(new None()); + + $database = new DatabaseDatabase(new MariaDB($register->get('db')), $cache); + $database->setNamespace('project_internal_'.$project->getId()); + + return $database; +}, ['register', 'project']); + +App::setResource('dbForExternal', function($register, $project) { + $cache = new Cache(new None()); + + $database = new DatabaseDatabase(new MariaDB($register->get('db')), $cache); + $database->setNamespace('project_external_'.$project->getId()); + + return $database; +}, ['register', 'project']); + App::setResource('mode', function($request) { /** @var Utopia\Swoole\Request $request */ return $request->getParam('mode', $request->getHeader('x-appwrite-mode', APP_MODE_DEFAULT)); diff --git a/composer.json b/composer.json index 4b68a5f1c7..8a7d39170c 100644 --- a/composer.json +++ b/composer.json @@ -42,9 +42,10 @@ "utopia-php/abuse": "0.4.*", "utopia-php/analytics": "0.2.*", "utopia-php/audit": "0.5.*", - "utopia-php/cache": "0.2.*", + "utopia-php/cache": "0.4.*", "utopia-php/cli": "0.10.*", "utopia-php/config": "0.2.*", + "utopia-php/database": "v0.x-dev", "utopia-php/locale": "0.3.*", "utopia-php/registry": "0.4.*", "utopia-php/preloader": "0.2.*", @@ -71,6 +72,10 @@ { "type": "git", "url": "https://github.com/appwrite/sdk-generator" + }, + { + "type": "git", + "url": "https://github.com/utopia-php/database" } ], "provide": { diff --git a/composer.lock b/composer.lock index 8e54ef9bbb..50dbbb380e 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "32ceddda707fb8f625f84eec08dc3871", + "content-hash": "4c699b84f6d1e789c9a45788634a702d", "packages": [ { "name": "adhocore/jwt", @@ -353,6 +353,79 @@ }, "time": "2020-11-06T16:09:14+00:00" }, + { + "name": "composer/package-versions-deprecated", + "version": "1.11.99.1", + "source": { + "type": "git", + "url": "https://github.com/composer/package-versions-deprecated.git", + "reference": "7413f0b55a051e89485c5cb9f765fe24bb02a7b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/7413f0b55a051e89485c5cb9f765fe24bb02a7b6", + "reference": "7413f0b55a051e89485c5cb9f765fe24bb02a7b6", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1.0 || ^2.0", + "php": "^7 || ^8" + }, + "replace": { + "ocramius/package-versions": "1.11.99" + }, + "require-dev": { + "composer/composer": "^1.9.3 || ^2.0@dev", + "ext-zip": "^1.13", + "phpunit/phpunit": "^6.5 || ^7" + }, + "type": "composer-plugin", + "extra": { + "class": "PackageVersions\\Installer", + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "PackageVersions\\": "src/PackageVersions" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be" + } + ], + "description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)", + "support": { + "issues": "https://github.com/composer/package-versions-deprecated/issues", + "source": "https://github.com/composer/package-versions-deprecated/tree/1.11.99.1" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2020-11-11T10:22:58+00:00" + }, { "name": "dragonmantank/cron-expression", "version": "v3.1.0", @@ -713,6 +786,61 @@ }, "time": "2020-12-26T17:45:17+00:00" }, + { + "name": "jean85/pretty-package-versions", + "version": "1.6.0", + "source": { + "type": "git", + "url": "https://github.com/Jean85/pretty-package-versions.git", + "reference": "1e0104b46f045868f11942aea058cd7186d6c303" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/1e0104b46f045868f11942aea058cd7186d6c303", + "reference": "1e0104b46f045868f11942aea058cd7186d6c303", + "shasum": "" + }, + "require": { + "composer/package-versions-deprecated": "^1.8.0", + "php": "^7.0|^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0|^8.5|^9.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Jean85\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alessandro Lai", + "email": "alessandro.lai85@gmail.com" + } + ], + "description": "A wrapper for ocramius/package-versions to get pretty versions strings", + "keywords": [ + "composer", + "package", + "release", + "versions" + ], + "support": { + "issues": "https://github.com/Jean85/pretty-package-versions/issues", + "source": "https://github.com/Jean85/pretty-package-versions/tree/1.6.0" + }, + "time": "2021-02-04T16:20:16+00:00" + }, { "name": "matomo/device-detector", "version": "4.2.2", @@ -782,6 +910,74 @@ }, "time": "2021-02-26T07:31:42+00:00" }, + { + "name": "mongodb/mongodb", + "version": "1.8.0", + "source": { + "type": "git", + "url": "https://github.com/mongodb/mongo-php-library.git", + "reference": "953dbc19443aa9314c44b7217a16873347e6840d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mongodb/mongo-php-library/zipball/953dbc19443aa9314c44b7217a16873347e6840d", + "reference": "953dbc19443aa9314c44b7217a16873347e6840d", + "shasum": "" + }, + "require": { + "ext-hash": "*", + "ext-json": "*", + "ext-mongodb": "^1.8.1", + "jean85/pretty-package-versions": "^1.2", + "php": "^7.0 || ^8.0", + "symfony/polyfill-php80": "^1.19" + }, + "require-dev": { + "squizlabs/php_codesniffer": "^3.5, <3.5.5", + "symfony/phpunit-bridge": "5.x-dev" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.8.x-dev" + } + }, + "autoload": { + "psr-4": { + "MongoDB\\": "src/" + }, + "files": [ + "src/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Andreas Braun", + "email": "andreas.braun@mongodb.com" + }, + { + "name": "Jeremy Mikola", + "email": "jmikola@gmail.com" + } + ], + "description": "MongoDB driver library", + "homepage": "https://jira.mongodb.org/browse/PHPLIB", + "keywords": [ + "database", + "driver", + "mongodb", + "persistence" + ], + "support": { + "issues": "https://github.com/mongodb/mongo-php-library/issues", + "source": "https://github.com/mongodb/mongo-php-library/tree/1.8.0" + }, + "time": "2020-11-25T12:26:02+00:00" + }, { "name": "mustangostang/spyc", "version": "0.6.3", @@ -1322,6 +1518,89 @@ ], "time": "2021-01-07T16:49:33+00:00" }, + { + "name": "symfony/polyfill-php80", + "version": "v1.22.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "dc3063ba22c2a1fd2f45ed856374d79114998f91" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/dc3063ba22c2a1fd2f45ed856374d79114998f91", + "reference": "dc3063ba22c2a1fd2f45ed856374d79114998f91", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.22-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.22.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-01-07T16:49:33+00:00" + }, { "name": "utopia-php/abuse", "version": "0.4.0", @@ -1483,21 +1762,22 @@ }, { "name": "utopia-php/cache", - "version": "0.2.3", + "version": "0.4.1", "source": { "type": "git", "url": "https://github.com/utopia-php/cache.git", - "reference": "a44b904127f88fa64673e402e5c0732ff6687d47" + "reference": "8c48eff73219c8c1ac2807909f0a38f3480c8938" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/cache/zipball/a44b904127f88fa64673e402e5c0732ff6687d47", - "reference": "a44b904127f88fa64673e402e5c0732ff6687d47", + "url": "https://api.github.com/repos/utopia-php/cache/zipball/8c48eff73219c8c1ac2807909f0a38f3480c8938", + "reference": "8c48eff73219c8c1ac2807909f0a38f3480c8938", "shasum": "" }, "require": { "ext-json": "*", - "php": ">=7.3" + "ext-redis": "*", + "php": ">=7.4" }, "require-dev": { "phpunit/phpunit": "^9.3", @@ -1529,9 +1809,9 @@ ], "support": { "issues": "https://github.com/utopia-php/cache/issues", - "source": "https://github.com/utopia-php/cache/tree/0.2.3" + "source": "https://github.com/utopia-php/cache/tree/0.4.1" }, - "time": "2020-10-24T10:11:01+00:00" + "time": "2021-04-29T18:41:43+00:00" }, { "name": "utopia-php/cli", @@ -1637,6 +1917,61 @@ }, "time": "2020-10-24T09:49:09+00:00" }, + { + "name": "utopia-php/database", + "version": "v0.x-dev", + "source": { + "type": "git", + "url": "https://github.com/utopia-php/database", + "reference": "518265a14702f61a35b95d4ae25c8b23e7c2b478" + }, + "require": { + "ext-mongodb": "*", + "ext-pdo": "*", + "ext-redis": "*", + "mongodb/mongodb": "1.8.0", + "php": ">=7.1", + "utopia-php/cache": "0.4.*", + "utopia-php/framework": "0.*.*" + }, + "require-dev": { + "phpunit/phpunit": "^9.4", + "vimeo/psalm": "4.0.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Utopia\\Database\\": "src/Database" + } + }, + "autoload-dev": { + "psr-4": { + "Utopia\\Tests\\": "tests/Database" + } + }, + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Eldad Fux", + "email": "eldad@appwrite.io" + }, + { + "name": "Brandon Leckemby", + "email": "brandon@appwrite.io" + } + ], + "description": "A simple library to manage application persistency using multiple database adapters", + "keywords": [ + "database", + "framework", + "php", + "upf", + "utopia" + ], + "time": "2021-05-02T18:26:42+00:00" + }, { "name": "utopia-php/domains", "version": "v1.1.0", @@ -2376,79 +2711,6 @@ "description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms", "time": "2021-03-28T22:18:36+00:00" }, - { - "name": "composer/package-versions-deprecated", - "version": "1.11.99.1", - "source": { - "type": "git", - "url": "https://github.com/composer/package-versions-deprecated.git", - "reference": "7413f0b55a051e89485c5cb9f765fe24bb02a7b6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/7413f0b55a051e89485c5cb9f765fe24bb02a7b6", - "reference": "7413f0b55a051e89485c5cb9f765fe24bb02a7b6", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.1.0 || ^2.0", - "php": "^7 || ^8" - }, - "replace": { - "ocramius/package-versions": "1.11.99" - }, - "require-dev": { - "composer/composer": "^1.9.3 || ^2.0@dev", - "ext-zip": "^1.13", - "phpunit/phpunit": "^6.5 || ^7" - }, - "type": "composer-plugin", - "extra": { - "class": "PackageVersions\\Installer", - "branch-alias": { - "dev-master": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "PackageVersions\\": "src/PackageVersions" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com" - }, - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be" - } - ], - "description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)", - "support": { - "issues": "https://github.com/composer/package-versions-deprecated/issues", - "source": "https://github.com/composer/package-versions-deprecated/tree/1.11.99.1" - }, - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" - } - ], - "time": "2020-11-11T10:22:58+00:00" - }, { "name": "composer/semver", "version": "3.2.4", @@ -4973,16 +5235,16 @@ }, { "name": "symfony/console", - "version": "v5.2.6", + "version": "v5.2.7", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "35f039df40a3b335ebf310f244cb242b3a83ac8d" + "reference": "90374b8ed059325b49a29b55b3f8bb4062c87629" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/35f039df40a3b335ebf310f244cb242b3a83ac8d", - "reference": "35f039df40a3b335ebf310f244cb242b3a83ac8d", + "url": "https://api.github.com/repos/symfony/console/zipball/90374b8ed059325b49a29b55b3f8bb4062c87629", + "reference": "90374b8ed059325b49a29b55b3f8bb4062c87629", "shasum": "" }, "require": { @@ -5050,7 +5312,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.2.6" + "source": "https://github.com/symfony/console/tree/v5.2.7" }, "funding": [ { @@ -5066,7 +5328,7 @@ "type": "tidelift" } ], - "time": "2021-03-28T09:42:18+00:00" + "time": "2021-04-19T14:07:32+00:00" }, { "name": "symfony/polyfill-intl-grapheme", @@ -5392,89 +5654,6 @@ ], "time": "2021-01-07T16:49:33+00:00" }, - { - "name": "symfony/polyfill-php80", - "version": "v1.22.1", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "dc3063ba22c2a1fd2f45ed856374d79114998f91" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/dc3063ba22c2a1fd2f45ed856374d79114998f91", - "reference": "dc3063ba22c2a1fd2f45ed856374d79114998f91", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.22-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Php80\\": "" - }, - "files": [ - "bootstrap.php" - ], - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ion Bazan", - "email": "ion.bazan@gmail.com" - }, - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.22.1" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-01-07T16:49:33+00:00" - }, { "name": "symfony/service-contracts", "version": "v2.4.0", @@ -5925,6 +6104,7 @@ "aliases": [], "minimum-stability": "stable", "stability-flags": { + "utopia-php/database": 20, "appwrite/sdk-generator": 20 }, "prefer-stable": false, diff --git a/src/Appwrite/Utopia/Response.php b/src/Appwrite/Utopia/Response.php index ef9d48f4ee..fd5716ec24 100644 --- a/src/Appwrite/Utopia/Response.php +++ b/src/Appwrite/Utopia/Response.php @@ -43,6 +43,7 @@ use Appwrite\Utopia\Response\Model\Webhook; use Appwrite\Utopia\Response\Model\Preferences; use Appwrite\Utopia\Response\Model\Mock; // Keep last use stdClass; +use Utopia\Database\Document as DatabaseDocument; /** * @method public function setStatusCode(int $code = 200): Response @@ -275,6 +276,27 @@ class Response extends SwooleResponse $this->json(!empty($output) ? $output : new stdClass()); } + /** + * Validate response objects and outputs + * the response according to given format type + * + * @param DatabaseDocument $document + * @param string $model + * + * return void + */ + public function dynamic2(DatabaseDocument $document, string $model): void + { + $output = $this->output2($document, $model); + + // If filter is set, parse the output + if(self::isFilter()){ + $output = self::getFilter()->parse($output, $model); + } + + $this->json(!empty($output) ? $output : new stdClass()); + } + /** * Generate valid response object from document data * @@ -327,6 +349,58 @@ class Response extends SwooleResponse return $this->payload; } + /** + * Generate valid response object from document data + * + * @param DatabaseDocument $document + * @param string $model + * + * return array + */ + public function output2(DatabaseDocument $document, string $model): array + { + $data = $document; + $model = $this->getModel($model); + $output = []; + + if ($model->isAny()) { + $this->payload = $document->getArrayCopy(); + return $this->payload; + } + + foreach ($model->getRules() as $key => $rule) { + if (!$document->isSet($key)) { + if (!is_null($rule['default'])) { + $document->setAttribute($key, $rule['default']); + } else { + throw new Exception('Model '.$model->getName().' is missing response key: '.$key); + } + } + + if ($rule['array']) { + if (!is_array($data[$key])) { + throw new Exception($key.' must be an array of type '.$rule['type']); + } + + foreach ($data[$key] as &$item) { + if ($item instanceof Document) { + if (!array_key_exists($rule['type'], $this->models)) { + throw new Exception('Missing model for rule: '. $rule['type']); + } + + $item = $this->output($item, $rule['type']); + } + } + } + + $output[$key] = $data[$key]; + } + + $this->payload = $output; + + return $this->payload; + } + /** * YAML * From eab18af07e50f74266f2bbd9dfc42f623281b1ef Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Mon, 3 May 2021 13:25:05 +0300 Subject: [PATCH 02/60] Added support for count --- app/controllers/api/storage.php | 6 ++---- composer.lock | 4 ++-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index ff9fe94675..d90a467026 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -179,11 +179,9 @@ App::get('/v1/storage/files') /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForInternal */ - $results = $dbForInternal->find('files', [], $limit, $offset); - $response->dynamic2(new Document([ - 'sum' => $limit, - 'files' => $results + 'sum' => $dbForInternal->count('files', [], 5000), + 'files' => $dbForInternal->find('files', [], $limit, $offset) ]), Response::MODEL_FILE_LIST); }); diff --git a/composer.lock b/composer.lock index 50dbbb380e..b15378c862 100644 --- a/composer.lock +++ b/composer.lock @@ -1923,7 +1923,7 @@ "source": { "type": "git", "url": "https://github.com/utopia-php/database", - "reference": "518265a14702f61a35b95d4ae25c8b23e7c2b478" + "reference": "81a16f851c712f5857c044c0e71c0abc698cd619" }, "require": { "ext-mongodb": "*", @@ -1970,7 +1970,7 @@ "upf", "utopia" ], - "time": "2021-05-02T18:26:42+00:00" + "time": "2021-05-03T09:59:14+00:00" }, { "name": "utopia-php/domains", From aa119d50d59bddb2743dfecab9df5543cb767d1c Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Mon, 3 May 2021 22:22:39 +0300 Subject: [PATCH 03/60] Added cache, updated db names --- app/controllers/api/projects.php | 4 ++-- app/init.php | 9 +++++---- app/views/console/storage/index.phtml | 4 ++-- composer.lock | 4 ++-- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index a3aeed15f9..c3ecb5e532 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -94,9 +94,9 @@ App::post('/v1/projects') $collections = Config::getParam('collections2', []); /** @var array $collections */ - $dbForInternal->setNamespace('project_internal_'.$project->getId()); + $dbForInternal->setNamespace('project_'.$project->getId().'_internal'); $dbForInternal->create(); - $dbForExternal->setNamespace('project_external_'.$project->getId()); + $dbForExternal->setNamespace('project_'.$project->getId().'_external'); $dbForExternal->create(); foreach ($collections as $key => $collection) { diff --git a/app/init.php b/app/init.php index 1af0027a8d..274b3381be 100644 --- a/app/init.php +++ b/app/init.php @@ -30,7 +30,7 @@ use Utopia\Registry\Registry; use MaxMind\Db\Reader; use PHPMailer\PHPMailer\PHPMailer; use PDO as PDONative; -use Utopia\Cache\Adapter\None; +use Utopia\Cache\Adapter\Redis as RedisCache; use Utopia\Cache\Cache; use Utopia\Database\Adapter\MariaDB; use Utopia\Database\Database as DatabaseDatabase; @@ -510,19 +510,20 @@ App::setResource('projectDB', function($register, $project) { }, ['register', 'project']); App::setResource('dbForInternal', function($register, $project) { - $cache = new Cache(new None()); + $cache = new Cache(new RedisCache($register->get('cache'))); $database = new DatabaseDatabase(new MariaDB($register->get('db')), $cache); - $database->setNamespace('project_internal_'.$project->getId()); + $database->setNamespace('project_'.$project->getId().'_internal'); return $database; }, ['register', 'project']); App::setResource('dbForExternal', function($register, $project) { - $cache = new Cache(new None()); + $cache = new Cache(new RedisCache($register->get('cache'))); $database = new DatabaseDatabase(new MariaDB($register->get('db')), $cache); $database->setNamespace('project_external_'.$project->getId()); + $database->setNamespace('project_'.$project->getId().'_external'); return $database; }, ['register', 'project']); diff --git a/app/views/console/storage/index.phtml b/app/views/console/storage/index.phtml index 05ed9453ea..803ee6f4ac 100644 --- a/app/views/console/storage/index.phtml +++ b/app/views/console/storage/index.phtml @@ -106,11 +106,11 @@ $fileLimitHuman = $this->getParam('fileLimitHuman', 0); - +
Add * for wildcard access
- +
Add * for wildcard access
diff --git a/composer.lock b/composer.lock index b15378c862..85756568b6 100644 --- a/composer.lock +++ b/composer.lock @@ -1923,7 +1923,7 @@ "source": { "type": "git", "url": "https://github.com/utopia-php/database", - "reference": "81a16f851c712f5857c044c0e71c0abc698cd619" + "reference": "43744d220c41048d677643c70a0aa3ab90652e13" }, "require": { "ext-mongodb": "*", @@ -1970,7 +1970,7 @@ "upf", "utopia" ], - "time": "2021-05-03T09:59:14+00:00" + "time": "2021-05-03T11:07:15+00:00" }, { "name": "utopia-php/domains", From d020b62051f6f0c7d6a934ed20ba87c443d07dc5 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Tue, 4 May 2021 00:05:25 +0300 Subject: [PATCH 04/60] Fixed mongo setup --- Dockerfile | 10 ++++++++++ app/preload.php | 1 + 2 files changed, 11 insertions(+) diff --git a/Dockerfile b/Dockerfile index 83f5c07b3f..7cff90c3cb 100755 --- a/Dockerfile +++ b/Dockerfile @@ -15,6 +15,7 @@ RUN composer update --ignore-platform-reqs --optimize-autoloader \ FROM php:8.0-cli-alpine as step1 ENV PHP_REDIS_VERSION=5.3.4 \ + PHP_MONGODB_VERSION=1.9.1 \ PHP_SWOOLE_VERSION=v4.6.6 \ PHP_IMAGICK_VERSION=master \ PHP_YAML_VERSION=2.2.1 \ @@ -45,6 +46,14 @@ RUN \ ./configure && \ make && make install && \ cd .. && \ + # Mongodb Extension + git clone --depth 1 --branch $PHP_MONGODB_VERSION https://github.com/mongodb/mongo-php-driver.git && \ + cd mongo-php-driver && \ + git submodule update --init && \ + phpize && \ + ./configure && \ + make && make install && \ + cd .. && \ ## Swoole Extension git clone --depth 1 --branch $PHP_SWOOLE_VERSION https://github.com/swoole/swoole-src.git && \ cd swoole-src && \ @@ -156,6 +165,7 @@ WORKDIR /usr/src/code COPY --from=step0 /usr/local/src/vendor /usr/src/code/vendor COPY --from=step1 /usr/local/lib/php/extensions/no-debug-non-zts-20200930/swoole.so /usr/local/lib/php/extensions/no-debug-non-zts-20200930/ COPY --from=step1 /usr/local/lib/php/extensions/no-debug-non-zts-20200930/redis.so /usr/local/lib/php/extensions/no-debug-non-zts-20200930/ +COPY --from=step1 /usr/local/lib/php/extensions/no-debug-non-zts-20200930/mongodb.so /usr/local/lib/php/extensions/no-debug-non-zts-20200930/ COPY --from=step1 /usr/local/lib/php/extensions/no-debug-non-zts-20200930/imagick.so /usr/local/lib/php/extensions/no-debug-non-zts-20200930/ COPY --from=step1 /usr/local/lib/php/extensions/no-debug-non-zts-20200930/yaml.so /usr/local/lib/php/extensions/no-debug-non-zts-20200930/ COPY --from=step1 /usr/local/lib/php/extensions/no-debug-non-zts-20200930/maxminddb.so /usr/local/lib/php/extensions/no-debug-non-zts-20200930/ diff --git a/app/preload.php b/app/preload.php index 7c8ae00938..352618ea99 100644 --- a/app/preload.php +++ b/app/preload.php @@ -32,6 +32,7 @@ foreach ([ realpath(__DIR__ . '/../vendor/psr/log'), realpath(__DIR__ . '/../vendor/matomo'), realpath(__DIR__ . '/../vendor/symfony'), + realpath(__DIR__ . '/../vendor/mongodb'), ] as $key => $value) { if($value !== false) { $preloader->ignore($value); From 507e0327e332032bb17528d2b6ba599e91718d5a Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Tue, 4 May 2021 01:20:40 +0300 Subject: [PATCH 05/60] Fixed storage tests --- app/controllers/general.php | 2 -- src/Appwrite/Utopia/Response/Model/File.php | 19 +++++++++++++------ tests/e2e/Services/Storage/StorageBase.php | 18 ++++++++---------- 3 files changed, 21 insertions(+), 18 deletions(-) diff --git a/app/controllers/general.php b/app/controllers/general.php index 47950c79c8..a4dd51618c 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -14,8 +14,6 @@ use Appwrite\Database\Database; use Appwrite\Database\Document; use Appwrite\Database\Validator\Authorization; use Appwrite\Network\Validator\Origin; -use Utopia\Storage\Device\Local; -use Utopia\Storage\Storage; use Appwrite\Utopia\Response\Filters\V06; use Utopia\CLI\Console; use Utopia\Database\Validator\Authorization as Authorization2; diff --git a/src/Appwrite/Utopia/Response/Model/File.php b/src/Appwrite/Utopia/Response/Model/File.php index 5d0d511539..15b1124dd6 100644 --- a/src/Appwrite/Utopia/Response/Model/File.php +++ b/src/Appwrite/Utopia/Response/Model/File.php @@ -16,12 +16,19 @@ class File extends Model 'default' => '', 'example' => '5e5ea5c16897e', ]) - ->addRule('$permissions', [ - 'type' => Response::MODEL_PERMISSIONS, - 'description' => 'File permissions.', - 'default' => new \stdClass, - 'example' => new \stdClass, - 'array' => false, + ->addRule('$read', [ + 'type' => self::TYPE_STRING, + 'description' => 'File read permissions.', + 'default' => [], + 'example' => ['*'], + 'array' => true, + ]) + ->addRule('$write', [ + 'type' => self::TYPE_STRING, + 'description' => 'File write permissions.', + 'default' => [], + 'example' => ['user:608f9da25e7e1'], + 'array' => true, ]) ->addRule('name', [ 'type' => self::TYPE_STRING, diff --git a/tests/e2e/Services/Storage/StorageBase.php b/tests/e2e/Services/Storage/StorageBase.php index 395eb8ce4f..09ec60724d 100644 --- a/tests/e2e/Services/Storage/StorageBase.php +++ b/tests/e2e/Services/Storage/StorageBase.php @@ -20,7 +20,6 @@ trait StorageBase 'file' => new CURLFile(realpath(__DIR__ . '/../../../resources/logo.png'), 'image/png', 'logo.png'), 'read' => ['*'], 'write' => ['*'], - 'folderId' => 'xyz', ]); $this->assertEquals($file['headers']['status-code'], 201); @@ -61,10 +60,10 @@ trait StorageBase //$this->assertEquals('aes-128-gcm', $file1['body']['fileOpenSSLCipher']); //$this->assertNotEmpty($file1['body']['fileOpenSSLTag']); //$this->assertNotEmpty($file1['body']['fileOpenSSLIV']); - $this->assertIsArray($file1['body']['$permissions']['read']); - $this->assertIsArray($file1['body']['$permissions']['write']); - $this->assertCount(1, $file1['body']['$permissions']['read']); - $this->assertCount(1, $file1['body']['$permissions']['write']); + $this->assertIsArray($file1['body']['$read']); + $this->assertIsArray($file1['body']['$write']); + $this->assertCount(1, $file1['body']['$read']); + $this->assertCount(1, $file1['body']['$write']); $file2 = $this->client->call(Client::METHOD_GET, '/storage/files/' . $data['fileId'] . '/preview', array_merge([ 'content-type' => 'application/json', @@ -101,7 +100,6 @@ trait StorageBase $this->assertEquals($image->getImageHeight(), $original->getImageHeight()); $this->assertEquals('PNG', $image->getImageFormat()); - $file4 = $this->client->call(Client::METHOD_GET, '/storage/files/' . $data['fileId'] . '/preview', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -203,10 +201,10 @@ trait StorageBase //$this->assertEquals('aes-128-gcm', $file['body']['fileOpenSSLCipher']); //$this->assertNotEmpty($file['body']['fileOpenSSLTag']); //$this->assertNotEmpty($file['body']['fileOpenSSLIV']); - $this->assertIsArray($file['body']['$permissions']['read']); - $this->assertIsArray($file['body']['$permissions']['write']); - $this->assertCount(1, $file['body']['$permissions']['read']); - $this->assertCount(1, $file['body']['$permissions']['write']); + $this->assertIsArray($file['body']['$read']); + $this->assertIsArray($file['body']['$write']); + $this->assertCount(1, $file['body']['$read']); + $this->assertCount(1, $file['body']['$write']); /** * Test for FAILURE From 779740184828dab0bf9adfa84612f893be401699 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Tue, 4 May 2021 12:31:26 +0300 Subject: [PATCH 06/60] Test console init --- app/controllers/general.php | 36 +++++++++++++++++++++++++++++++++--- app/init.php | 10 +++++++++- composer.lock | 30 +++++++++++++++--------------- 3 files changed, 57 insertions(+), 19 deletions(-) diff --git a/app/controllers/general.php b/app/controllers/general.php index a4dd51618c..dcf205ce52 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -22,15 +22,45 @@ Config::setParam('domainVerification', false); Config::setParam('cookieDomain', 'localhost'); Config::setParam('cookieSamesite', Response::COOKIE_SAMESITE_NONE); -App::init(function ($utopia, $request, $response, $console, $project, $user, $locale, $clients) { +App::init(function ($utopia, $request, $response, $console, $project, $user, $locale, $clients, $dbForConsole) { /** @var Utopia\Swoole\Request $request */ /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Document $console */ /** @var Appwrite\Database\Document $project */ /** @var Appwrite\Database\Document $user */ /** @var Utopia\Locale\Locale $locale */ - /** @var bool $mode */ /** @var array $clients */ + /** @var Utopia\Database\Database $dbForConsole */ + + if(!$dbForConsole->exists()) { + + Console::success('init database!!'); + + $collections = Config::getParam('collections2', []); /** @var array $collections */ + + $dbForConsole->create(); + + foreach ($collections as $key => $collection) { + $dbForConsole->createCollection($key); + + foreach ($collection['attributes'] as $i => $attribute) { + $dbForConsole->createAttribute( + $key, + $attribute['$id'], + $attribute['type'], + $attribute['size'], + $attribute['required'], + $attribute['signed'], + $attribute['array'], + $attribute['filters'], + ); + } + + foreach ($collection['indexes'] as $i => $index) { + + } + } + } $localeParam = (string)$request->getParam('locale', $request->getHeader('x-appwrite-locale', '')); @@ -230,7 +260,7 @@ App::init(function ($utopia, $request, $response, $console, $project, $user, $lo throw new Exception('Password reset is required', 412); } -}, ['utopia', 'request', 'response', 'console', 'project', 'user', 'locale', 'clients']); +}, ['utopia', 'request', 'response', 'console', 'project', 'user', 'locale', 'clients', 'dbForConsole']); App::options(function ($request, $response) { /** @var Utopia\Swoole\Request $request */ diff --git a/app/init.php b/app/init.php index 274b3381be..d874b05394 100644 --- a/app/init.php +++ b/app/init.php @@ -522,12 +522,20 @@ App::setResource('dbForExternal', function($register, $project) { $cache = new Cache(new RedisCache($register->get('cache'))); $database = new DatabaseDatabase(new MariaDB($register->get('db')), $cache); - $database->setNamespace('project_external_'.$project->getId()); $database->setNamespace('project_'.$project->getId().'_external'); return $database; }, ['register', 'project']); +App::setResource('dbForConsole', function($register, $project) { + $cache = new Cache(new RedisCache($register->get('cache'))); + + $database = new DatabaseDatabase(new MariaDB($register->get('db')), $cache); + $database->setNamespace('project_console_internal'); + + return $database; +}, ['register', 'project']); + App::setResource('mode', function($request) { /** @var Utopia\Swoole\Request $request */ return $request->getParam('mode', $request->getHeader('x-appwrite-mode', APP_MODE_DEFAULT)); diff --git a/composer.lock b/composer.lock index 85756568b6..4fa4972aa0 100644 --- a/composer.lock +++ b/composer.lock @@ -1211,16 +1211,16 @@ }, { "name": "psr/log", - "version": "1.1.3", + "version": "1.1.4", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc" + "reference": "d49695b909c3b7628b6289db5479a1c204601f11" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/0f73288fd15629204f9d42b7055f72dacbe811fc", - "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc", + "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11", "shasum": "" }, "require": { @@ -1244,7 +1244,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common interface for logging libraries", @@ -1255,9 +1255,9 @@ "psr-3" ], "support": { - "source": "https://github.com/php-fig/log/tree/1.1.3" + "source": "https://github.com/php-fig/log/tree/1.1.4" }, - "time": "2020-03-23T09:12:05+00:00" + "time": "2021-05-03T11:20:27+00:00" }, { "name": "ralouphie/getallheaders", @@ -1923,7 +1923,7 @@ "source": { "type": "git", "url": "https://github.com/utopia-php/database", - "reference": "43744d220c41048d677643c70a0aa3ab90652e13" + "reference": "2299091c63aec2315a4f40bc8dd16968c5bec6bf" }, "require": { "ext-mongodb": "*", @@ -1970,7 +1970,7 @@ "upf", "utopia" ], - "time": "2021-05-03T11:07:15+00:00" + "time": "2021-05-03T22:49:00+00:00" }, { "name": "utopia-php/domains", @@ -3305,16 +3305,16 @@ }, { "name": "nikic/php-parser", - "version": "v4.10.4", + "version": "v4.10.5", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "c6d052fc58cb876152f89f532b95a8d7907e7f0e" + "reference": "4432ba399e47c66624bc73c8c0f811e5c109576f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/c6d052fc58cb876152f89f532b95a8d7907e7f0e", - "reference": "c6d052fc58cb876152f89f532b95a8d7907e7f0e", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/4432ba399e47c66624bc73c8c0f811e5c109576f", + "reference": "4432ba399e47c66624bc73c8c0f811e5c109576f", "shasum": "" }, "require": { @@ -3355,9 +3355,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.10.4" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.10.5" }, - "time": "2020-12-20T10:01:03+00:00" + "time": "2021-05-03T19:11:20+00:00" }, { "name": "openlss/lib-array2xml", From abf90d0c72b7f0eddf99ff4b8df3465ead7c683b Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Tue, 4 May 2021 13:24:08 +0300 Subject: [PATCH 07/60] Added console init on setup --- app/controllers/general.php | 35 ++--------------------------------- app/http.php | 36 ++++++++++++++++++++++++++++++++++++ app/init.php | 6 +++--- 3 files changed, 41 insertions(+), 36 deletions(-) diff --git a/app/controllers/general.php b/app/controllers/general.php index dcf205ce52..4f483e4055 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -22,7 +22,7 @@ Config::setParam('domainVerification', false); Config::setParam('cookieDomain', 'localhost'); Config::setParam('cookieSamesite', Response::COOKIE_SAMESITE_NONE); -App::init(function ($utopia, $request, $response, $console, $project, $user, $locale, $clients, $dbForConsole) { +App::init(function ($utopia, $request, $response, $console, $project, $user, $locale, $clients) { /** @var Utopia\Swoole\Request $request */ /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Document $console */ @@ -30,37 +30,6 @@ App::init(function ($utopia, $request, $response, $console, $project, $user, $lo /** @var Appwrite\Database\Document $user */ /** @var Utopia\Locale\Locale $locale */ /** @var array $clients */ - /** @var Utopia\Database\Database $dbForConsole */ - - if(!$dbForConsole->exists()) { - - Console::success('init database!!'); - - $collections = Config::getParam('collections2', []); /** @var array $collections */ - - $dbForConsole->create(); - - foreach ($collections as $key => $collection) { - $dbForConsole->createCollection($key); - - foreach ($collection['attributes'] as $i => $attribute) { - $dbForConsole->createAttribute( - $key, - $attribute['$id'], - $attribute['type'], - $attribute['size'], - $attribute['required'], - $attribute['signed'], - $attribute['array'], - $attribute['filters'], - ); - } - - foreach ($collection['indexes'] as $i => $index) { - - } - } - } $localeParam = (string)$request->getParam('locale', $request->getHeader('x-appwrite-locale', '')); @@ -260,7 +229,7 @@ App::init(function ($utopia, $request, $response, $console, $project, $user, $lo throw new Exception('Password reset is required', 412); } -}, ['utopia', 'request', 'response', 'console', 'project', 'user', 'locale', 'clients', 'dbForConsole']); +}, ['utopia', 'request', 'response', 'console', 'project', 'user', 'locale', 'clients']); App::options(function ($request, $response) { /** @var Utopia\Swoole\Request $request */ diff --git a/app/http.php b/app/http.php index 23679a90bb..ec8fffd025 100644 --- a/app/http.php +++ b/app/http.php @@ -12,6 +12,7 @@ use Swoole\Http\Request as SwooleRequest; use Swoole\Http\Response as SwooleResponse; use Utopia\App; use Utopia\CLI\Console; +use Utopia\Config\Config; use Utopia\Database\Validator\Authorization as Authorization2; // xdebug_start_trace('/tmp/trace'); @@ -51,6 +52,41 @@ $http->on('AfterReload', function($serv, $workerId) { $http->on('start', function (Server $http) use ($payloadSize) { + /** @var Utopia\Database\Database $dbForConsole */ + $app = new App('UTC'); + $dbForConsole = $app->getResource('dbForConsole'); + + if(!$dbForConsole->exists()) { + Console::success('[Setup] - Server database init started...'); + + $collections = Config::getParam('collections2', []); /** @var array $collections */ + + $dbForConsole->create(); + + foreach ($collections as $key => $collection) { + $dbForConsole->createCollection($key); + + foreach ($collection['attributes'] as $i => $attribute) { + $dbForConsole->createAttribute( + $key, + $attribute['$id'], + $attribute['type'], + $attribute['size'], + $attribute['required'], + $attribute['signed'], + $attribute['array'], + $attribute['filters'], + ); + } + + foreach ($collection['indexes'] as $i => $index) { + + } + } + + Console::success('[Setup] - Server database init completed...'); + } + Console::success('Server started succefully (max payload is '.number_format($payloadSize).' bytes)'); Console::info("Master pid {$http->master_pid}, manager pid {$http->manager_pid}"); diff --git a/app/init.php b/app/init.php index d874b05394..666bf59f56 100644 --- a/app/init.php +++ b/app/init.php @@ -527,14 +527,14 @@ App::setResource('dbForExternal', function($register, $project) { return $database; }, ['register', 'project']); -App::setResource('dbForConsole', function($register, $project) { +App::setResource('dbForConsole', function($register) { $cache = new Cache(new RedisCache($register->get('cache'))); $database = new DatabaseDatabase(new MariaDB($register->get('db')), $cache); - $database->setNamespace('project_console_internal'); + $database->setNamespace('project_console'); return $database; -}, ['register', 'project']); +}, ['register']); App::setResource('mode', function($request) { /** @var Utopia\Swoole\Request $request */ From d699c8aba9c1da1304ffab614be7123d78bca86d Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Wed, 5 May 2021 00:25:17 +0300 Subject: [PATCH 08/60] Updated functions service --- app/config/collections2.php | 1591 ++++++----------------------- app/controllers/api/functions.php | 304 +++--- app/init.php | 29 +- composer.lock | 4 +- 4 files changed, 467 insertions(+), 1461 deletions(-) diff --git a/app/config/collections2.php b/app/config/collections2.php index 7391493885..b29c8b505c 100644 --- a/app/config/collections2.php +++ b/app/config/collections2.php @@ -6,15 +6,6 @@ use Utopia\Database\Database; $providers = Config::getParam('providers', []); $auth = Config::getParam('auth', []); -// '$id' => 'title', -// 'type' => Database::VAR_STRING, -// 'format' => '', -// 'size' => 256, -// 'required' => true, -// 'signed' => true, -// 'array' => false, -// 'filters' => [], - $collections = [ 'users' => [ '$collection' => Database::COLLECTIONS, @@ -146,976 +137,7 @@ $collections = [ ], 'indexes' => [], ], - // Database::SYSTEM_COLLECTION_SESSIONS => [ - // '$collection' => Database::SYSTEM_COLLECTION_COLLECTIONS, - // '$id' => Database::SYSTEM_COLLECTION_SESSIONS, - // '$permissions' => ['read' => ['*']], - // 'name' => 'Session', - // 'structure' => true, - // 'rules' => [ - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'User ID', - // 'key' => 'userId', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => null, - // 'required' => true, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Provider', - // 'key' => 'provider', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => '', - // 'required' => true, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Provider User Identifier', - // 'key' => 'providerUid', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => '', - // 'required' => false, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Provider Token', - // 'key' => 'providerToken', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => '', - // 'required' => false, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Secret', - // 'key' => 'secret', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => '', - // 'required' => true, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Expire', - // 'key' => 'expire', - // 'type' => Database::SYSTEM_VAR_TYPE_NUMERIC, - // 'default' => 0, - // 'required' => true, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'User Agent', - // 'key' => 'userAgent', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => '', - // 'required' => true, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'IP', - // 'key' => 'ip', - // 'type' => Database::SYSTEM_VAR_TYPE_IP, - // 'default' => '', - // 'required' => true, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'OS Code', - // 'key' => 'osCode', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => '', - // 'required' => false, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'OS Name', - // 'key' => 'osName', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => '', - // 'required' => false, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'OS Version', - // 'key' => 'osVersion', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => '', - // 'required' => false, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Client Type', - // 'key' => 'clientType', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => '', - // 'required' => false, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Client Code', - // 'key' => 'clientCode', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => '', - // 'required' => false, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Client Name', - // 'key' => 'clientName', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => '', - // 'required' => false, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Client Version', - // 'key' => 'clientVersion', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => '', - // 'required' => false, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Client Engine', - // 'key' => 'clientEngine', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => '', - // 'required' => false, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Client Engine Version', - // 'key' => 'clientEngineVersion', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => '', - // 'required' => false, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Device Name', - // 'key' => 'deviceName', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => '', - // 'required' => false, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Device Brand', - // 'key' => 'deviceBrand', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => '', - // 'required' => false, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Device Model', - // 'key' => 'deviceModel', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => '', - // 'required' => false, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Country Code', - // 'key' => 'countryCode', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => '', - // 'required' => false, - // 'array' => false, - // ], - // ], - // ], - // Database::SYSTEM_COLLECTION_TOKENS => [ - // '$collection' => Database::SYSTEM_COLLECTION_COLLECTIONS, - // '$id' => Database::SYSTEM_COLLECTION_TOKENS, - // '$permissions' => ['read' => ['*']], - // 'name' => 'Token', - // 'structure' => true, - // 'rules' => [ - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'User ID', - // 'key' => 'userId', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => null, - // 'required' => false, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Type', - // 'key' => 'type', - // 'type' => Database::SYSTEM_VAR_TYPE_NUMERIC, - // 'default' => null, - // 'required' => true, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Secret', - // 'key' => 'secret', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => '', - // 'required' => true, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Expire', - // 'key' => 'expire', - // 'type' => Database::SYSTEM_VAR_TYPE_NUMERIC, - // 'default' => 0, - // 'required' => true, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'User Agent', - // 'key' => 'userAgent', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => '', - // 'required' => true, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'IP', - // 'key' => 'ip', - // 'type' => Database::SYSTEM_VAR_TYPE_IP, - // 'default' => '', - // 'required' => true, - // 'array' => false, - // ], - // ], - // ], - // Database::SYSTEM_COLLECTION_MEMBERSHIPS => [ - // '$collection' => Database::SYSTEM_COLLECTION_COLLECTIONS, - // '$id' => Database::SYSTEM_COLLECTION_MEMBERSHIPS, - // '$permissions' => ['read' => ['*']], - // 'name' => 'Membership', - // 'structure' => true, - // 'rules' => [ - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Team ID', - // 'key' => 'teamId', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => null, - // 'required' => true, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'User ID', - // 'key' => 'userId', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => null, - // 'required' => true, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Roles', - // 'key' => 'roles', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => '', - // 'required' => true, - // 'array' => true, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Invited', - // 'key' => 'invited', - // 'type' => Database::SYSTEM_VAR_TYPE_NUMERIC, - // 'default' => 0, - // 'required' => false, //FIXME SHOULD BE REQUIRED - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Joined', - // 'key' => 'joined', - // 'type' => Database::SYSTEM_VAR_TYPE_NUMERIC, - // 'default' => 0, - // 'required' => true, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Confirm', - // 'key' => 'confirm', - // 'type' => Database::SYSTEM_VAR_TYPE_BOOLEAN, - // 'default' => '', - // 'required' => true, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Secret', - // 'key' => 'secret', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => '', - // 'required' => false, - // 'array' => false, - // ], - // ], - // ], - // Database::SYSTEM_COLLECTION_TEAMS => [ - // '$collection' => Database::SYSTEM_COLLECTION_COLLECTIONS, - // '$id' => Database::SYSTEM_COLLECTION_TEAMS, - // '$permissions' => ['read' => ['*']], - // 'name' => 'Team', - // 'structure' => true, - // 'rules' => [ - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Name', - // 'key' => 'name', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => '', - // 'required' => true, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Date Created', - // 'key' => 'dateCreated', - // 'type' => Database::SYSTEM_VAR_TYPE_NUMERIC, - // 'default' => '', - // 'required' => false, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Sum', - // 'key' => 'sum', - // 'type' => Database::SYSTEM_VAR_TYPE_NUMERIC, - // 'default' => '', - // 'required' => false, - // 'array' => false, - // ], - // ], - // ], - // Database::SYSTEM_COLLECTION_PROJECTS => [ - // '$collection' => Database::SYSTEM_COLLECTION_COLLECTIONS, - // '$id' => Database::SYSTEM_COLLECTION_PROJECTS, - // '$permissions' => ['read' => ['*']], - // 'name' => 'Project', - // 'structure' => true, - // 'rules' => [ - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Name', - // 'key' => 'name', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => null, - // 'required' => true, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Description', - // 'key' => 'description', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => null, - // 'required' => false, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Logo', - // 'key' => 'logo', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => '', - // 'required' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'URL', - // 'key' => 'url', - // 'type' => Database::SYSTEM_VAR_TYPE_URL, - // 'default' => '', - // 'required' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Team ID', - // 'key' => 'teamId', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => 0, - // 'required' => true, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Legal Name', - // 'key' => 'legalName', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => '', - // 'required' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Legal Country', - // 'key' => 'legalCountry', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => '', - // 'required' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Legal State', - // 'key' => 'legalState', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => '', - // 'required' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Legal City', - // 'key' => 'legalCity', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => '', - // 'required' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Legal Address', - // 'key' => 'legalAddress', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => '', - // 'required' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Legal Tax ID', - // 'key' => 'legalTaxId', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => '', - // 'required' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Max users allowed', - // 'key' => 'usersAuthLimit', - // 'type' => Database::SYSTEM_VAR_TYPE_NUMERIC, - // 'default' => 0, - // 'required' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Webhooks', - // 'key' => 'webhooks', - // 'type' => Database::SYSTEM_VAR_TYPE_DOCUMENT, - // 'default' => [], - // 'required' => false, - // 'array' => true, - // 'list' => [Database::SYSTEM_COLLECTION_WEBHOOKS], - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'API Keys', - // 'key' => 'keys', - // 'type' => Database::SYSTEM_VAR_TYPE_DOCUMENT, - // 'default' => [], - // 'required' => false, - // 'array' => true, - // 'list' => [Database::SYSTEM_COLLECTION_KEYS], - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Tasks', - // 'key' => 'tasks', - // 'type' => Database::SYSTEM_VAR_TYPE_DOCUMENT, - // 'default' => [], - // 'required' => false, - // 'array' => true, - // 'list' => [Database::SYSTEM_COLLECTION_TASKS], - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Platforms', - // 'key' => 'platforms', - // 'type' => Database::SYSTEM_VAR_TYPE_DOCUMENT, - // 'default' => [], - // 'required' => false, - // 'array' => true, - // 'list' => [Database::SYSTEM_COLLECTION_PLATFORMS], - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Domains', - // 'key' => 'domains', - // 'type' => Database::SYSTEM_VAR_TYPE_DOCUMENT, - // 'default' => [], - // 'required' => false, - // 'array' => true, - // 'list' => [Database::SYSTEM_COLLECTION_DOMAINS], - // ], - // ], - // ], - // Database::SYSTEM_COLLECTION_WEBHOOKS => [ - // '$collection' => Database::SYSTEM_COLLECTION_COLLECTIONS, - // '$id' => Database::SYSTEM_COLLECTION_WEBHOOKS, - // '$permissions' => ['read' => ['*']], - // 'name' => 'Webhook', - // 'structure' => true, - // 'rules' => [ - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Name', - // 'key' => 'name', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => null, - // 'required' => true, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Events', - // 'key' => 'events', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => null, - // 'required' => false, - // 'array' => true, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'URL', - // 'key' => 'url', - // 'type' => Database::SYSTEM_VAR_TYPE_URL, - // 'default' => '', - // 'required' => false, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Security', - // 'key' => 'security', - // 'type' => Database::SYSTEM_VAR_TYPE_BOOLEAN, - // 'default' => '', - // 'required' => false, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'HTTP User', - // 'key' => 'httpUser', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => '', - // 'required' => false, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'HTTP Password', - // 'key' => 'httpPass', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => '', - // 'required' => false, - // 'array' => false, - // 'filter' => ['encrypt'], - // ], - // ], - // ], - // Database::SYSTEM_COLLECTION_KEYS => [ - // '$collection' => Database::SYSTEM_COLLECTION_COLLECTIONS, - // '$id' => Database::SYSTEM_COLLECTION_KEYS, - // '$permissions' => ['read' => ['*']], - // 'name' => 'Key', - // 'structure' => true, - // 'rules' => [ - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Name', - // 'key' => 'name', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => null, - // 'required' => true, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Scopes', - // 'key' => 'scopes', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => null, - // 'required' => false, - // 'array' => true, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Secret', - // 'key' => 'secret', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => '', - // 'required' => false, - // 'filter' => ['encrypt'], - // ], - // ], - // ], - // Database::SYSTEM_COLLECTION_TASKS => [ - // '$collection' => Database::SYSTEM_COLLECTION_COLLECTIONS, - // '$id' => Database::SYSTEM_COLLECTION_TASKS, - // '$permissions' => ['read' => ['*']], - // 'name' => 'Task', - // 'structure' => true, - // 'rules' => [ - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Name', - // 'key' => 'name', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => null, - // 'required' => true, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Status', - // 'key' => 'status', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => '', - // 'required' => false, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Updated', - // 'key' => 'updated', - // 'type' => Database::SYSTEM_VAR_TYPE_NUMERIC, - // 'default' => '', - // 'required' => false, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Schedule', - // 'key' => 'schedule', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => '', - // 'required' => false, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Previous', - // 'key' => 'previous', - // 'type' => Database::SYSTEM_VAR_TYPE_NUMERIC, - // 'default' => '', - // 'required' => false, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Next', - // 'key' => 'next', - // 'type' => Database::SYSTEM_VAR_TYPE_NUMERIC, - // 'default' => '', - // 'required' => false, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Duration', - // 'key' => 'duration', - // 'type' => Database::SYSTEM_VAR_TYPE_NUMERIC, - // 'default' => '', - // 'required' => false, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Delay', - // 'key' => 'delay', - // 'type' => Database::SYSTEM_VAR_TYPE_NUMERIC, - // 'default' => '', - // 'required' => false, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Security', - // 'key' => 'security', - // 'type' => Database::SYSTEM_VAR_TYPE_BOOLEAN, - // 'default' => '', - // 'required' => false, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'HTTP Method', - // 'key' => 'httpMethod', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => '', - // 'required' => false, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'HTTP URL', - // 'key' => 'httpUrl', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => '', - // 'required' => false, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'HTTP Headers', - // 'key' => 'httpHeaders', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => null, - // 'required' => false, - // 'array' => true, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'HTTP User', - // 'key' => 'httpUser', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => '', - // 'required' => false, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'HTTP Password', - // 'key' => 'httpPass', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => '', - // 'required' => false, - // 'array' => false, - // 'filter' => ['encrypt'], - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Log', - // 'key' => 'log', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => '', - // 'required' => false, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Failures', - // 'key' => 'failures', - // 'type' => Database::SYSTEM_VAR_TYPE_NUMERIC, - // 'default' => '', - // 'required' => false, - // 'array' => false, - // ], - // ], - // ], - // Database::SYSTEM_COLLECTION_PLATFORMS => [ - // '$collection' => Database::SYSTEM_COLLECTION_COLLECTIONS, - // '$id' => Database::SYSTEM_COLLECTION_PLATFORMS, - // '$permissions' => ['read' => ['*']], - // 'name' => 'Platform', - // 'structure' => true, - // 'rules' => [ - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Type', - // 'key' => 'type', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => null, - // 'required' => true, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Name', - // 'key' => 'name', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => null, - // 'required' => true, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Date Created', - // 'key' => 'dateCreated', - // 'type' => Database::SYSTEM_VAR_TYPE_NUMERIC, - // 'default' => 0, - // 'required' => false, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Date Updated', - // 'key' => 'dateUpdated', - // 'type' => Database::SYSTEM_VAR_TYPE_NUMERIC, - // 'default' => 0, - // 'required' => false, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Key', - // 'key' => 'key', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => '', - // 'required' => false, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Store', - // 'key' => 'store', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => '', - // 'required' => false, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Hostname', - // 'key' => 'hostname', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => '', - // 'required' => false, - // 'array' => false, - // ], - // ], - // ], - // Database::SYSTEM_COLLECTION_DOMAINS => [ - // '$collection' => Database::SYSTEM_COLLECTION_COLLECTIONS, - // '$id' => Database::SYSTEM_COLLECTION_DOMAINS, - // '$permissions' => ['read' => ['*']], - // 'name' => 'Domains', - // 'structure' => true, - // 'rules' => [ - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Domain', - // 'key' => 'domain', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => null, - // 'required' => true, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Updated', - // 'key' => 'updated', - // 'type' => Database::SYSTEM_VAR_TYPE_NUMERIC, - // 'default' => 0, - // 'required' => false, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Top Level Domain', - // 'key' => 'tld', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => '', - // 'required' => false, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Registerable Domain', - // 'key' => 'registerable', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => '', - // 'required' => false, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Verification', - // 'key' => 'verification', - // 'type' => Database::SYSTEM_VAR_TYPE_BOOLEAN, - // 'default' => false, - // 'required' => true, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Certificate ID', - // 'key' => 'certificateId', - // 'type' => Database::SYSTEM_VAR_TYPE_KEY, - // 'default' => '', - // 'required' => false, - // 'array' => false, - // ], - // ], - // ], - // Database::SYSTEM_COLLECTION_CERTIFICATES => [ - // '$collection' => Database::SYSTEM_COLLECTION_COLLECTIONS, - // '$id' => Database::SYSTEM_COLLECTION_CERTIFICATES, - // '$permissions' => ['read' => ['*']], - // 'name' => 'Certificates', - // 'structure' => true, - // 'rules' => [ - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Domain', - // 'key' => 'domain', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => null, - // 'required' => true, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Issue Date', - // 'key' => 'issueDate', - // 'type' => Database::SYSTEM_VAR_TYPE_NUMERIC, - // 'default' => 0, - // 'required' => false, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Renew Date', - // 'key' => 'renewDate', - // 'type' => Database::SYSTEM_VAR_TYPE_NUMERIC, - // 'default' => 0, - // 'required' => false, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Attempts', - // 'key' => 'attempts', - // 'type' => Database::SYSTEM_VAR_TYPE_NUMERIC, - // 'default' => 0, - // 'required' => false, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Log', - // 'key' => 'log', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => '', - // 'required' => false, - // 'array' => false, - // ], - // ], - // ], + 'files' => [ '$collection' => Database::COLLECTIONS, '$id' => 'files', @@ -1143,7 +165,6 @@ $collections = [ 'filters' => [], ], [ - 'array' => false, '$id' => 'name', 'type' => Database::VAR_STRING, 'format' => '', @@ -1266,316 +287,306 @@ $collections = [ ], 'indexes' => [], ], - // Database::SYSTEM_COLLECTION_FUNCTIONS => [ - // '$collection' => Database::SYSTEM_COLLECTION_COLLECTIONS, - // '$id' => Database::SYSTEM_COLLECTION_FUNCTIONS, - // '$permissions' => ['read' => ['*']], - // 'name' => 'Functions', - // 'structure' => true, - // 'rules' => [ - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Date Created', - // 'key' => 'dateCreated', - // 'type' => Database::SYSTEM_VAR_TYPE_NUMERIC, - // 'default' => 0, - // 'required' => false, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Date Updated', - // 'key' => 'dateUpdated', - // 'type' => Database::SYSTEM_VAR_TYPE_NUMERIC, - // 'default' => 0, - // 'required' => false, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Status', - // 'key' => 'status', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => '', - // 'required' => false, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Name', - // 'key' => 'name', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => '', - // 'required' => false, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Env', - // 'key' => 'env', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => '', - // 'required' => false, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Tag', - // 'key' => 'tag', - // 'type' => Database::SYSTEM_VAR_TYPE_KEY, - // 'default' => '', - // 'required' => false, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Vars', - // 'key' => 'vars', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => '', - // 'required' => false, - // 'array' => false, - // 'filter' => ['json', 'encrypt'] - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Events', - // 'key' => 'events', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => '', - // 'required' => false, - // 'array' => true, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Schedule', - // 'key' => 'schedule', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => '', - // 'required' => false, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Schedule Previous Run', - // 'key' => 'schedulePrevious', - // 'type' => Database::SYSTEM_VAR_TYPE_NUMERIC, - // 'default' => '', - // 'required' => false, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Schedule Next Run', - // 'key' => 'scheduleNext', - // 'type' => Database::SYSTEM_VAR_TYPE_NUMERIC, - // 'default' => '', - // 'required' => false, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Timeout', - // 'key' => 'timeout', - // 'type' => Database::SYSTEM_VAR_TYPE_NUMERIC, - // 'default' => '', - // 'required' => false, - // 'array' => false, - // ], - // ], - // ], - // Database::SYSTEM_COLLECTION_TAGS => [ - // '$collection' => Database::SYSTEM_COLLECTION_COLLECTIONS, - // '$id' => Database::SYSTEM_COLLECTION_TAGS, - // '$permissions' => ['read' => ['*']], - // 'name' => 'Tags', - // 'structure' => true, - // 'rules' => [ - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Date Created', - // 'key' => 'dateCreated', - // 'type' => Database::SYSTEM_VAR_TYPE_NUMERIC, - // 'default' => 0, - // 'required' => false, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Function ID', - // 'key' => 'functionId', - // 'type' => Database::SYSTEM_VAR_TYPE_KEY, - // 'default' => '', - // 'required' => false, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Command', - // 'key' => 'command', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => '', - // 'required' => false, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Code Path', - // 'key' => 'path', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => '', - // 'required' => false, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Code Size', - // 'key' => 'size', - // 'type' => Database::SYSTEM_VAR_TYPE_NUMERIC, - // 'default' => '', - // 'required' => false, - // 'array' => false, - // ], - // ], - // ], - // Database::SYSTEM_COLLECTION_EXECUTIONS => [ - // '$collection' => Database::SYSTEM_COLLECTION_COLLECTIONS, - // '$id' => Database::SYSTEM_COLLECTION_EXECUTIONS, - // '$permissions' => ['read' => ['*']], - // 'name' => 'Executions', - // 'structure' => true, - // 'rules' => [ - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Date Created', - // 'key' => 'dateCreated', - // 'type' => Database::SYSTEM_VAR_TYPE_NUMERIC, - // 'default' => 0, - // 'required' => false, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Function ID', - // 'key' => 'functionId', - // 'type' => Database::SYSTEM_VAR_TYPE_KEY, - // 'default' => '', - // 'required' => false, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Tag ID', - // 'key' => 'tagId', - // 'type' => Database::SYSTEM_VAR_TYPE_KEY, - // 'default' => '', - // 'required' => false, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Trigger', - // 'key' => 'trigger', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => '', - // 'required' => false, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Status', - // 'key' => 'status', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => '', - // 'required' => false, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Exit Code', - // 'key' => 'exitCode', - // 'type' => Database::SYSTEM_VAR_TYPE_NUMERIC, - // 'default' => '', - // 'required' => false, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Stdout', - // 'key' => 'stdout', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => '', - // 'required' => false, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Stderr', - // 'key' => 'stderr', - // 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - // 'default' => '', - // 'required' => false, - // 'array' => false, - // ], - // [ - // '$collection' => Database::SYSTEM_COLLECTION_RULES, - // 'label' => 'Time', - // 'key' => 'time', - // 'type' => Database::SYSTEM_VAR_TYPE_NUMERIC, - // 'default' => '', - // 'required' => false, - // 'array' => false, - // ], - // ], - // ], - // Database::SYSTEM_COLLECTION_RESERVED => [ - // '$collection' => Database::SYSTEM_COLLECTION_COLLECTIONS, - // '$id' => Database::SYSTEM_COLLECTION_RESERVED, - // '$permissions' => ['read' => ['*']], - // 'name' => 'Reserved', - // 'structure' => true, - // ], + + 'functions' => [ + '$collection' => Database::COLLECTIONS, + '$id' => 'functions', + 'name' => 'Functions', + 'attributes' => [ + [ + '$id' => 'execute', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 128, + 'signed' => true, + 'required' => false, + 'array' => true, + 'filters' => [], + ], + [ + '$id' => 'dateCreated', + 'type' => Database::VAR_INTEGER, + 'format' => '', + 'size' => 0, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'dateUpdated', + 'type' => Database::VAR_INTEGER, + 'format' => '', + 'size' => 0, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + [ + 'array' => false, + '$id' => 'status', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'name', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 2048, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'env', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 2048, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'tag', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'vars', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 8192, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => ['json', 'encrypt'], + ], + [ + '$id' => 'events', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 256, + 'signed' => true, + 'required' => false, + 'array' => true, + 'filters' => [], + ], + [ + '$id' => 'schedule', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 128, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'schedulePrevious', + 'type' => Database::VAR_INTEGER, + 'format' => '', + 'size' => 0, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'scheduleNext', + 'type' => Database::VAR_INTEGER, + 'format' => '', + 'size' => 0, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'timeout', + 'type' => Database::VAR_INTEGER, + 'format' => '', + 'size' => 0, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + ], + 'indexes' => [], + ], + + 'tags' => [ + '$collection' => Database::COLLECTIONS, + '$id' => 'tags', + 'name' => 'Tags', + 'attributes' => [ + [ + '$id' => 'dateCreated', + 'type' => Database::VAR_INTEGER, + 'format' => '', + 'size' => 0, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'functionId', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + [ + 'array' => false, + '$id' => 'command', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 2048, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'path', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 2048, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'size', + 'type' => Database::VAR_INTEGER, + 'format' => '', + 'size' => 0, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + ], + 'indexes' => [], + ], + + 'executions' => [ + '$collection' => Database::COLLECTIONS, + '$id' => 'executions', + 'name' => 'Executions', + 'attributes' => [ + [ + '$id' => 'dateCreated', + 'type' => Database::VAR_INTEGER, + 'format' => '', + 'size' => 0, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'functionId', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'tagId', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + [ + 'array' => false, + '$id' => 'trigger', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 128, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'status', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 128, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'stdout', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 16384, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'stderr', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 16384, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'exitCode', + 'type' => Database::VAR_INTEGER, + 'format' => '', + 'size' => 0, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'time', + 'type' => Database::VAR_FLOAT, + 'format' => '', + 'size' => 0, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + ], + 'indexes' => [], + ], ]; -/* - * Add enabled OAuth2 providers to default data rules - */ -// foreach ($providers as $index => $provider) { -// if (!$provider['enabled']) { -// continue; -// } - -// $collections[Database::SYSTEM_COLLECTION_PROJECTS]['rules'][] = [ -// '$collection' => Database::SYSTEM_COLLECTION_RULES, -// 'label' => 'OAuth2 '.\ucfirst($index).' ID', -// 'key' => 'usersOauth2'.\ucfirst($index).'Appid', -// 'type' => Database::SYSTEM_VAR_TYPE_TEXT, -// 'default' => '', -// 'required' => false, -// 'array' => false, -// ]; - -// $collections[Database::SYSTEM_COLLECTION_PROJECTS]['rules'][] = [ -// '$collection' => Database::SYSTEM_COLLECTION_RULES, -// 'label' => 'OAuth2 '.\ucfirst($index).' Secret', -// 'key' => 'usersOauth2'.\ucfirst($index).'Secret', -// 'type' => Database::SYSTEM_VAR_TYPE_TEXT, -// 'default' => '', -// 'required' => false, -// 'array' => false, -// 'filter' => ['encrypt'], -// ]; -// } - -// foreach ($auth as $index => $method) { -// $collections[Database::SYSTEM_COLLECTION_PROJECTS]['rules'][] = [ -// '$collection' => Database::SYSTEM_COLLECTION_RULES, -// 'label' => $method['name'] || '', -// 'key' => $method['key'] || '', -// 'type' => Database::SYSTEM_VAR_TYPE_BOOLEAN, -// 'default' => true, -// 'required' => false, -// ]; -// } - return $collections; \ No newline at end of file diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index ff8b7d5bc9..84d5bc9219 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -2,9 +2,6 @@ use Ahc\Jwt\JWT; use Appwrite\Auth\Auth; -use Appwrite\Database\Database; -use Appwrite\Database\Document; -use Appwrite\Database\Validator\Authorization; use Appwrite\Database\Validator\UID; use Utopia\Storage\Storage; use Utopia\Storage\Validator\File; @@ -14,6 +11,10 @@ use Utopia\Storage\Validator\Upload; use Appwrite\Utopia\Response; use Appwrite\Task\Validator\Cron; use Utopia\App; +use Utopia\Exception; +use Utopia\Database\Document; +use Utopia\Database\Query; +use Utopia\Database\Validator\Authorization; use Utopia\Validator\ArrayList; use Utopia\Validator\Assoc; use Utopia\Validator\Text; @@ -21,7 +22,6 @@ use Utopia\Validator\Range; use Utopia\Validator\WhiteList; use Utopia\Config\Config; use Cron\CronExpression; -use Utopia\Exception; include_once __DIR__ . '/../shared/api.php'; @@ -45,16 +45,13 @@ App::post('/v1/functions') ->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 ($name, $execute, $env, $vars, $events, $schedule, $timeout, $response, $projectDB) { + ->inject('dbForInternal') + ->action(function ($name, $execute, $env, $vars, $events, $schedule, $timeout, $response, $dbForInternal) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Database $dbForInternal */ - $function = $projectDB->createDocument([ - '$collection' => Database::SYSTEM_COLLECTION_FUNCTIONS, - '$permissions' => [ - 'execute' => $execute, - ], + $function = $dbForInternal->createDocument('functions', new Document([ + 'execute' => $execute, 'dateCreated' => time(), 'dateUpdated' => time(), 'status' => 'disabled', @@ -64,10 +61,10 @@ App::post('/v1/functions') 'vars' => $vars, 'events' => $events, 'schedule' => $schedule, - 'schedulePrevious' => null, - 'scheduleNext' => null, + 'schedulePrevious' => 0, + 'scheduleNext' => 0, 'timeout' => $timeout, - ]); + ])); if (false === $function) { throw new Exception('Failed saving function to DB', 500); @@ -75,7 +72,7 @@ App::post('/v1/functions') $response ->setStatusCode(Response::STATUS_CODE_CREATED) - ->dynamic($function, Response::MODEL_FUNCTION) + ->dynamic2($function, Response::MODEL_FUNCTION) ; }); @@ -95,24 +92,14 @@ App::get('/v1/functions') ->param('offset', 0, new Range(0, 2000), 'Results offset. The default value is 0. Use this param to manage pagination.', true) ->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true) ->inject('response') - ->inject('projectDB') - ->action(function ($search, $limit, $offset, $orderType, $response, $projectDB) { + ->inject('dbForInternal') + ->action(function ($search, $limit, $offset, $orderType, $response, $dbForInternal) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Database $dbForInternal */ - $results = $projectDB->getCollection([ - 'limit' => $limit, - 'offset' => $offset, - 'orderType' => $orderType, - 'search' => $search, - 'filters' => [ - '$collection='.Database::SYSTEM_COLLECTION_FUNCTIONS, - ], - ]); - - $response->dynamic(new Document([ - 'sum' => $projectDB->getSum(), - 'functions' => $results + $response->dynamic2(new Document([ + 'sum' => $dbForInternal->count('functions', [], 5000), + 'functions' => $dbForInternal->find('functions', [], $limit, $offset, [], []) ]), Response::MODEL_FUNCTION_LIST); }); @@ -129,18 +116,18 @@ App::get('/v1/functions/:functionId') ->label('sdk.response.model', Response::MODEL_FUNCTION) ->param('functionId', '', new UID(), 'Function unique ID.') ->inject('response') - ->inject('projectDB') - ->action(function ($functionId, $response, $projectDB) { + ->inject('dbForInternal') + ->action(function ($functionId, $response, $dbForInternal) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Database $dbForInternal */ - $function = $projectDB->getDocument($functionId); + $function = $dbForInternal->getDocument('functions', $functionId); - if (empty($function->getId()) || Database::SYSTEM_COLLECTION_FUNCTIONS != $function->getCollection()) { + if (empty($function->getId())) { throw new Exception('Function not found', 404); } - $response->dynamic($function, Response::MODEL_FUNCTION); + $response->dynamic2($function, Response::MODEL_FUNCTION); }); App::get('/v1/functions/:functionId/usage') @@ -154,18 +141,18 @@ App::get('/v1/functions/:functionId/usage') ->param('range', '30d', new WhiteList(['24h', '7d', '30d', '90d']), 'Date range.', true) ->inject('response') ->inject('project') - ->inject('projectDB') + ->inject('dbForInternal') ->inject('register') - ->action(function ($functionId, $range, $response, $project, $projectDB, $register) { + ->action(function ($functionId, $range, $response, $project, $dbForInternal, $register) { /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Document $project */ - /** @var Appwrite\Database\Database $consoleDB */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Database $consoleDB */ + /** @var Utopia\Database\Database $dbForInternal */ /** @var Utopia\Registry\Registry $register */ - $function = $projectDB->getDocument($functionId); + $function = $dbForInternal->getDocument('functions', $functionId); - if (empty($function->getId()) || Database::SYSTEM_COLLECTION_FUNCTIONS != $function->getCollection()) { + if (empty($function->getId())) { throw new Exception('Function not found', 404); } @@ -284,35 +271,33 @@ App::put('/v1/functions/:functionId') ->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') + ->inject('dbForInternal') ->inject('project') - ->action(function ($functionId, $name, $execute, $vars, $events, $schedule, $timeout, $response, $projectDB, $project) { + ->action(function ($functionId, $name, $execute, $vars, $events, $schedule, $timeout, $response, $dbForInternal, $project) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Database $dbForInternal */ /** @var Appwrite\Database\Document $project */ - $function = $projectDB->getDocument($functionId); + $function = $dbForInternal->getDocument('functions', $functionId); - if (empty($function->getId()) || Database::SYSTEM_COLLECTION_FUNCTIONS != $function->getCollection()) { + if (empty($function->getId())) { throw new Exception('Function not found', 404); } $original = $function->getAttribute('schedule', ''); $cron = (!empty($function->getAttribute('tag', null)) && !empty($schedule)) ? new CronExpression($schedule) : null; - $next = (!empty($function->getAttribute('tag', null)) && !empty($schedule)) ? $cron->getNextRunDate()->format('U') : null; + $next = (!empty($function->getAttribute('tag', null)) && !empty($schedule)) ? $cron->getNextRunDate()->format('U') : 0; - $function = $projectDB->updateDocument(array_merge($function->getArrayCopy(), [ - '$permissions' => [ - 'execute' => $execute, - ], + $function = $dbForInternal->updateDocument('functions', $function->getId(), new Document(array_merge($function->getArrayCopy(), [ + 'execute' => $execute, 'dateUpdated' => time(), 'name' => $name, 'vars' => $vars, 'events' => $events, 'schedule' => $schedule, - 'scheduleNext' => $next, + 'scheduleNext' => (int)$next, 'timeout' => $timeout, - ])); + ]))); if (false === $function) { throw new Exception('Failed saving function to DB', 500); @@ -327,7 +312,7 @@ App::put('/v1/functions/:functionId') ]); // Async task rescheduale } - $response->dynamic($function, Response::MODEL_FUNCTION); + $response->dynamic2($function, Response::MODEL_FUNCTION); }); App::patch('/v1/functions/:functionId/tag') @@ -345,32 +330,32 @@ App::patch('/v1/functions/:functionId/tag') ->param('functionId', '', new UID(), 'Function unique ID.') ->param('tag', '', new UID(), 'Tag unique ID.') ->inject('response') - ->inject('projectDB') + ->inject('dbForInternal') ->inject('project') - ->action(function ($functionId, $tag, $response, $projectDB, $project) { + ->action(function ($functionId, $tag, $response, $dbForInternal, $project) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Database $dbForInternal */ /** @var Appwrite\Database\Document $project */ - $function = $projectDB->getDocument($functionId); - $tag = $projectDB->getDocument($tag); + $function = $dbForInternal->getDocument('functions', $functionId); + $tag = $dbForInternal->getDocument('tags', $tag); - if (empty($function->getId()) || Database::SYSTEM_COLLECTION_FUNCTIONS != $function->getCollection()) { + if (empty($function->getId())) { throw new Exception('Function not found', 404); } - if (empty($tag->getId()) || Database::SYSTEM_COLLECTION_TAGS != $tag->getCollection()) { + if (empty($tag->getId())) { throw new Exception('Tag not found', 404); } $schedule = $function->getAttribute('schedule', ''); $cron = (empty($function->getAttribute('tag')) && !empty($schedule)) ? new CronExpression($schedule) : null; - $next = (empty($function->getAttribute('tag')) && !empty($schedule)) ? $cron->getNextRunDate()->format('U') : null; + $next = (empty($function->getAttribute('tag')) && !empty($schedule)) ? $cron->getNextRunDate()->format('U') : 0; - $function = $projectDB->updateDocument(array_merge($function->getArrayCopy(), [ + $function = $dbForInternal->updateDocument('functions', $function->getId(), new Document(array_merge($function->getArrayCopy(), [ 'tag' => $tag->getId(), - 'scheduleNext' => $next, - ])); + 'scheduleNext' => (int)$next, + ]))); if ($next) { // Init first schedule ResqueScheduler::enqueueAt($next, 'v1-functions', 'FunctionsV1', [ @@ -385,7 +370,7 @@ App::patch('/v1/functions/:functionId/tag') throw new Exception('Failed saving function to DB', 500); } - $response->dynamic($function, Response::MODEL_FUNCTION); + $response->dynamic2($function, Response::MODEL_FUNCTION); }); App::delete('/v1/functions/:functionId') @@ -401,20 +386,20 @@ App::delete('/v1/functions/:functionId') ->label('sdk.response.model', Response::MODEL_NONE) ->param('functionId', '', new UID(), 'Function unique ID.') ->inject('response') - ->inject('projectDB') + ->inject('dbForInternal') ->inject('deletes') - ->action(function ($functionId, $response, $projectDB, $deletes) { + ->action(function ($functionId, $response, $dbForInternal, $deletes) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Database $dbForInternal */ /** @var Appwrite\Event\Event $deletes */ - $function = $projectDB->getDocument($functionId); + $function = $dbForInternal->getDocument('functions', $functionId); - if (empty($function->getId()) || Database::SYSTEM_COLLECTION_FUNCTIONS != $function->getCollection()) { + if (empty($function->getId())) { throw new Exception('Function not found', 404); } - if (!$projectDB->deleteDocument($function->getId())) { + if (!$dbForInternal->deleteDocument('functions', $function->getId())) { throw new Exception('Failed to remove function from DB', 500); } @@ -445,17 +430,17 @@ App::post('/v1/functions/:functionId/tags') ->param('code', [], new File(), 'Gzip file with your code package. When used with the Appwrite CLI, pass the path to your code directory, and the CLI will automatically package your code. Use a path that is within the current directory.', false) ->inject('request') ->inject('response') - ->inject('projectDB') + ->inject('dbForInternal') ->inject('usage') - ->action(function ($functionId, $command, $file, $request, $response, $projectDB, $usage) { + ->action(function ($functionId, $command, $file, $request, $response, $dbForInternal, $usage) { /** @var Utopia\Swoole\Request $request */ /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Database $dbForInternal */ /** @var Appwrite\Event\Event $usage */ - $function = $projectDB->getDocument($functionId); + $function = $dbForInternal->getDocument('functions', $functionId); - if (empty($function->getId()) || Database::SYSTEM_COLLECTION_FUNCTIONS != $function->getCollection()) { + if (empty($function->getId())) { throw new Exception('Function not found', 404); } @@ -494,18 +479,15 @@ App::post('/v1/functions/:functionId/tags') throw new Exception('Failed moving file', 500); } - $tag = $projectDB->createDocument([ - '$collection' => Database::SYSTEM_COLLECTION_TAGS, - '$permissions' => [ - 'read' => [], - 'write' => [], - ], + $tag = $dbForInternal->createDocument('tags', new Document([ + '$read' => [], + '$write' => [], 'functionId' => $function->getId(), 'dateCreated' => time(), 'command' => $command, 'path' => $path, 'size' => $size, - ]); + ])); if (false === $tag) { throw new Exception('Failed saving tag to DB', 500); @@ -517,7 +499,7 @@ App::post('/v1/functions/:functionId/tags') $response ->setStatusCode(Response::STATUS_CODE_CREATED) - ->dynamic($tag, Response::MODEL_TAG) + ->dynamic2($tag, Response::MODEL_TAG) ; }); @@ -538,30 +520,27 @@ App::get('/v1/functions/:functionId/tags') ->param('offset', 0, new Range(0, 2000), 'Results offset. The default value is 0. Use this param to manage pagination.', true) ->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true) ->inject('response') - ->inject('projectDB') - ->action(function ($functionId, $search, $limit, $offset, $orderType, $response, $projectDB) { + ->inject('dbForInternal') + ->action(function ($functionId, $search, $limit, $offset, $orderType, $response, $dbForInternal) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Database $dbForInternal */ - $function = $projectDB->getDocument($functionId); + $function = $dbForInternal->getDocument('functions', $functionId); - if (empty($function->getId()) || Database::SYSTEM_COLLECTION_FUNCTIONS != $function->getCollection()) { + if (empty($function->getId())) { throw new Exception('Function not found', 404); } - $results = $projectDB->getCollection([ - 'limit' => $limit, - 'offset' => $offset, - 'orderType' => $orderType, - 'search' => $search, - 'filters' => [ - '$collection='.Database::SYSTEM_COLLECTION_TAGS, - 'functionId='.$function->getId(), - ], - ]); + $results = $dbForInternal->find('tags', [ + new Query('functionId', Query::TYPE_EQUAL, [$function->getId()]), + ], $limit, $offset); + + $sum = $dbForInternal->count('tags', [ + new Query('functionId', Query::TYPE_EQUAL, [$function->getId()]), + ], 5000); - $response->dynamic(new Document([ - 'sum' => $projectDB->getSum(), + $response->dynamic2(new Document([ + 'sum' => $sum, 'tags' => $results ]), Response::MODEL_TAG_LIST); }); @@ -580,28 +559,28 @@ App::get('/v1/functions/:functionId/tags/:tagId') ->param('functionId', '', new UID(), 'Function unique ID.') ->param('tagId', '', new UID(), 'Tag unique ID.') ->inject('response') - ->inject('projectDB') - ->action(function ($functionId, $tagId, $response, $projectDB) { + ->inject('dbForInternal') + ->action(function ($functionId, $tagId, $response, $dbForInternal) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Database $dbForInternal */ - $function = $projectDB->getDocument($functionId); + $function = $dbForInternal->getDocument('functions', $functionId); - if (empty($function->getId()) || Database::SYSTEM_COLLECTION_FUNCTIONS != $function->getCollection()) { + if (empty($function->getId())) { throw new Exception('Function not found', 404); } - $tag = $projectDB->getDocument($tagId); + $tag = $dbForInternal->getDocument('tags', $tagId); if ($tag->getAttribute('functionId') !== $function->getId()) { throw new Exception('Tag not found', 404); } - if (empty($tag->getId()) || Database::SYSTEM_COLLECTION_TAGS != $tag->getCollection()) { + if (empty($tag->getId())) { throw new Exception('Tag not found', 404); } - $response->dynamic($tag, Response::MODEL_TAG); + $response->dynamic2($tag, Response::MODEL_TAG); }); App::delete('/v1/functions/:functionId/tags/:tagId') @@ -618,41 +597,41 @@ App::delete('/v1/functions/:functionId/tags/:tagId') ->param('functionId', '', new UID(), 'Function unique ID.') ->param('tagId', '', new UID(), 'Tag unique ID.') ->inject('response') - ->inject('projectDB') + ->inject('dbForInternal') ->inject('usage') - ->action(function ($functionId, $tagId, $response, $projectDB, $usage) { + ->action(function ($functionId, $tagId, $response, $dbForInternal, $usage) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Database $dbForInternal */ /** @var Appwrite\Event\Event $usage */ - $function = $projectDB->getDocument($functionId); + $function = $dbForInternal->getDocument('functions', $functionId); - if (empty($function->getId()) || Database::SYSTEM_COLLECTION_FUNCTIONS != $function->getCollection()) { + if (empty($function->getId())) { throw new Exception('Function not found', 404); } - $tag = $projectDB->getDocument($tagId); + $tag = $dbForInternal->getDocument('tags', $tagId); if ($tag->getAttribute('functionId') !== $function->getId()) { throw new Exception('Tag not found', 404); } - if (empty($tag->getId()) || Database::SYSTEM_COLLECTION_TAGS != $tag->getCollection()) { + if (empty($tag->getId())) { throw new Exception('Tag not found', 404); } $device = Storage::getDevice('functions'); if ($device->delete($tag->getAttribute('path', ''))) { - if (!$projectDB->deleteDocument($tag->getId())) { + if (!$dbForInternal->deleteDocument('tags', $tag->getId())) { throw new Exception('Failed to remove tag from DB', 500); } } if($function->getAttribute('tag') === $tag->getId()) { // Reset function tag - $function = $projectDB->updateDocument(array_merge($function->getArrayCopy(), [ + $function = $dbForInternal->updateDocument('functions', $function->getId(), new Document(array_merge($function->getArrayCopy(), [ 'tag' => '', - ])); + ]))); if (false === $function) { throw new Exception('Failed saving function to DB', 500); @@ -685,29 +664,29 @@ App::post('/v1/functions/:functionId/executions') // ->param('async', 1, new Range(0, 1), 'Execute code asynchronously. Pass 1 for true, 0 for false. Default value is 1.', true) ->inject('response') ->inject('project') - ->inject('projectDB') + ->inject('dbForInternal') ->inject('user') - ->action(function ($functionId, $data, /*$async,*/ $response, $project, $projectDB, $user) { + ->action(function ($functionId, $data, /*$async,*/ $response, $project, $dbForInternal, $user) { /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Document $project */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Database $dbForInternal */ /** @var Appwrite\Database\Document $user */ Authorization::disable(); - $function = $projectDB->getDocument($functionId); + $function = $dbForInternal->getDocument('functions', $functionId); - if (empty($function->getId()) || Database::SYSTEM_COLLECTION_FUNCTIONS != $function->getCollection()) { + if (empty($function->getId())) { throw new Exception('Function not found', 404); } - $tag = $projectDB->getDocument($function->getAttribute('tag')); + $tag = $dbForInternal->getDocument('tags', $function->getAttribute('tag')); if ($tag->getAttribute('functionId') !== $function->getId()) { throw new Exception('Tag not found. Deploy tag before trying to execute a function', 404); } - if (empty($tag->getId()) || Database::SYSTEM_COLLECTION_TAGS != $tag->getCollection()) { + if (empty($tag->getId())) { throw new Exception('Tag not found. Deploy tag before trying to execute a function', 404); } @@ -715,27 +694,25 @@ App::post('/v1/functions/:functionId/executions') $validator = new Authorization($function, 'execute'); - if (!$validator->isValid($function->getPermissions())) { // Check if user has write access to execute function + if (!$validator->isValid($function->getAttribute('execute'))) { // 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' => (!empty($user->getId())) ? ['user:' . $user->getId()] : [], - 'write' => [], - ], + $execution = $dbForInternal->createDocument('executions', new Document([ + '$read' => (!empty($user->getId())) ? ['user:' . $user->getId()] : [], + '$write' => [], 'dateCreated' => time(), 'functionId' => $function->getId(), + 'tagId' => $tag->getId(), 'trigger' => 'http', // http / schedule / event 'status' => 'waiting', // waiting / processing / completed / failed 'exitCode' => 0, 'stdout' => '', 'stderr' => '', - 'time' => 0, - ]); + 'time' => 0.0, + ])); Authorization::reset(); @@ -776,7 +753,7 @@ App::post('/v1/functions/:functionId/executions') $response ->setStatusCode(Response::STATUS_CODE_CREATED) - ->dynamic($execution, Response::MODEL_EXECUTION) + ->dynamic2($execution, Response::MODEL_EXECUTION) ; }); @@ -797,32 +774,29 @@ App::get('/v1/functions/:functionId/executions') ->param('offset', 0, new Range(0, 2000), 'Results offset. The default value is 0. Use this param to manage pagination.', true) ->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true) ->inject('response') - ->inject('projectDB') - ->action(function ($functionId, $search, $limit, $offset, $orderType, $response, $projectDB) { + ->inject('dbForInternal') + ->action(function ($functionId, $search, $limit, $offset, $orderType, $response, $dbForInternal) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Database $dbForInternal */ Authorization::disable(); - $function = $projectDB->getDocument($functionId); + $function = $dbForInternal->getDocument('functions', $functionId); Authorization::reset(); - if (empty($function->getId()) || Database::SYSTEM_COLLECTION_FUNCTIONS != $function->getCollection()) { + if (empty($function->getId())) { throw new Exception('Function not found', 404); } - $results = $projectDB->getCollection([ - 'limit' => $limit, - 'offset' => $offset, - 'orderType' => $orderType, - 'search' => $search, - 'filters' => [ - '$collection='.Database::SYSTEM_COLLECTION_EXECUTIONS, - 'functionId='.$function->getId(), - ], - ]); + $results = $dbForInternal->find('executions', [ + new Query('functionId', Query::TYPE_EQUAL, [$function->getId()]), + ], $limit, $offset); + + $sum = $dbForInternal->count('executions', [ + new Query('functionId', Query::TYPE_EQUAL, [$function->getId()]), + ], 5000); - $response->dynamic(new Document([ - 'sum' => $projectDB->getSum(), + $response->dynamic2(new Document([ + 'sum' => $sum, 'executions' => $results ]), Response::MODEL_EXECUTION_LIST); }); @@ -841,28 +815,28 @@ App::get('/v1/functions/:functionId/executions/:executionId') ->param('functionId', '', new UID(), 'Function unique ID.') ->param('executionId', '', new UID(), 'Execution unique ID.') ->inject('response') - ->inject('projectDB') - ->action(function ($functionId, $executionId, $response, $projectDB) { + ->inject('dbForInternal') + ->action(function ($functionId, $executionId, $response, $dbForInternal) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Database $dbForInternal */ Authorization::disable(); - $function = $projectDB->getDocument($functionId); + $function = $dbForInternal->getDocument('functions', $functionId); Authorization::reset(); - if (empty($function->getId()) || Database::SYSTEM_COLLECTION_FUNCTIONS != $function->getCollection()) { + if (empty($function->getId())) { throw new Exception('Function not found', 404); } - $execution = $projectDB->getDocument($executionId); + $execution = $dbForInternal->getDocument('executions', $executionId); if ($execution->getAttribute('functionId') !== $function->getId()) { throw new Exception('Execution not found', 404); } - if (empty($execution->getId()) || Database::SYSTEM_COLLECTION_EXECUTIONS != $execution->getCollection()) { + if (empty($execution->getId())) { throw new Exception('Execution not found', 404); } - $response->dynamic($execution, Response::MODEL_EXECUTION); + $response->dynamic2($execution, Response::MODEL_EXECUTION); }); diff --git a/app/init.php b/app/init.php index 666bf59f56..2990fc6276 100644 --- a/app/init.php +++ b/app/init.php @@ -33,7 +33,7 @@ use PDO as PDONative; use Utopia\Cache\Adapter\Redis as RedisCache; use Utopia\Cache\Cache; use Utopia\Database\Adapter\MariaDB; -use Utopia\Database\Database as DatabaseDatabase; +use Utopia\Database\Database as Database2; use Utopia\Database\Validator\Authorization as Authorization2; const APP_NAME = 'Appwrite'; @@ -147,6 +147,27 @@ Database::addFilter('encrypt', } ); +Database2::addFilter('encrypt', + function($value) { + $key = App::getEnv('_APP_OPENSSL_KEY_V1'); + $iv = OpenSSL::randomPseudoBytes(OpenSSL::cipherIVLength(OpenSSL::CIPHER_AES_128_GCM)); + $tag = null; + return json_encode([ + 'data' => OpenSSL::encrypt($value, OpenSSL::CIPHER_AES_128_GCM, $key, 0, $iv, $tag), + 'method' => OpenSSL::CIPHER_AES_128_GCM, + 'iv' => bin2hex($iv), + 'tag' => bin2hex($tag), + 'version' => '1', + ]); + }, + function($value) { + $value = json_decode($value, true); + $key = App::getEnv('_APP_OPENSSL_KEY_V'.$value['version']); + + return OpenSSL::decrypt($value['data'], $value['method'], $key, 0, hex2bin($value['iv']), hex2bin($value['tag'])); + } +); + /* * Registry */ @@ -512,7 +533,7 @@ App::setResource('projectDB', function($register, $project) { App::setResource('dbForInternal', function($register, $project) { $cache = new Cache(new RedisCache($register->get('cache'))); - $database = new DatabaseDatabase(new MariaDB($register->get('db')), $cache); + $database = new Database2(new MariaDB($register->get('db')), $cache); $database->setNamespace('project_'.$project->getId().'_internal'); return $database; @@ -521,7 +542,7 @@ App::setResource('dbForInternal', function($register, $project) { App::setResource('dbForExternal', function($register, $project) { $cache = new Cache(new RedisCache($register->get('cache'))); - $database = new DatabaseDatabase(new MariaDB($register->get('db')), $cache); + $database = new Database2(new MariaDB($register->get('db')), $cache); $database->setNamespace('project_'.$project->getId().'_external'); return $database; @@ -530,7 +551,7 @@ App::setResource('dbForExternal', function($register, $project) { App::setResource('dbForConsole', function($register) { $cache = new Cache(new RedisCache($register->get('cache'))); - $database = new DatabaseDatabase(new MariaDB($register->get('db')), $cache); + $database = new Database2(new MariaDB($register->get('db')), $cache); $database->setNamespace('project_console'); return $database; diff --git a/composer.lock b/composer.lock index 4fa4972aa0..997d6156df 100644 --- a/composer.lock +++ b/composer.lock @@ -1923,7 +1923,7 @@ "source": { "type": "git", "url": "https://github.com/utopia-php/database", - "reference": "2299091c63aec2315a4f40bc8dd16968c5bec6bf" + "reference": "52c8cacf1d7930c7ae4cad034666a4322e3c56b0" }, "require": { "ext-mongodb": "*", @@ -1970,7 +1970,7 @@ "upf", "utopia" ], - "time": "2021-05-03T22:49:00+00:00" + "time": "2021-05-04T21:14:11+00:00" }, { "name": "utopia-php/domains", From f24ae683e6c3048b6d475550bcd34759526e14fc Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Wed, 5 May 2021 00:45:41 +0300 Subject: [PATCH 09/60] Migrated functions worker --- app/workers/functions.php | 66 ++++++++++++++++----------------------- 1 file changed, 27 insertions(+), 39 deletions(-) diff --git a/app/workers/functions.php b/app/workers/functions.php index 7ea356464e..ad49ed6125 100644 --- a/app/workers/functions.php +++ b/app/workers/functions.php @@ -1,16 +1,17 @@ args['userId'] ?? ''; $jwt = $this->args['jwt'] ?? ''; - $database = new Database(); - $database->setAdapter(new RedisAdapter(new MySQLAdapter($register), $register)); - $database->setNamespace('app_'.$projectId); - $database->setMocks(Config::getParam('collections', [])); + $cache = new Cache(new Redis($register->get('cache'))); + $database = new Database(new MariaDB($register->get('db')), $cache); + $database->setNamespace('project_'.$projectId.'_internal'); switch ($trigger) { case 'event': @@ -166,16 +166,7 @@ class FunctionsV1 Authorization::disable(); - $functions = $database->getCollection([ - 'limit' => $limit, - 'offset' => $offset, - 'orderField' => 'name', - 'orderType' => 'ASC', - 'orderCast' => 'string', - 'filters' => [ - '$collection='.Database::SYSTEM_COLLECTION_FUNCTIONS, - ], - ]); + $functions = $database->find('functions', [], $limit, $offset, ['name'], [Database::ORDER_ASC]); Authorization::reset(); @@ -217,10 +208,10 @@ class FunctionsV1 // Reschedule Authorization::disable(); - $function = $database->getDocument($functionId); + $function = $database->getDocument('functions', $functionId); Authorization::reset(); - if (empty($function->getId()) || Database::SYSTEM_COLLECTION_FUNCTIONS != $function->getCollection()) { + if (empty($function->getId())) { throw new Exception('Function not found ('.$functionId.')'); } @@ -238,9 +229,9 @@ class FunctionsV1 Authorization::disable(); - $function = $database->updateDocument(array_merge($function->getArrayCopy(), [ - 'scheduleNext' => $next, - ])); + $function = $database->updateDocument('functions', $function->getId(), new Document(array_merge($function->getArrayCopy(), [ + 'scheduleNext' => (int)$next, + ]))); Authorization::reset(); @@ -257,10 +248,10 @@ class FunctionsV1 case 'http': Authorization::disable(); - $function = $database->getDocument($functionId); + $function = $database->getDocument('functions', $functionId); Authorization::reset(); - if (empty($function->getId()) || Database::SYSTEM_COLLECTION_FUNCTIONS != $function->getCollection()) { + if (empty($function->getId())) { throw new Exception('Function not found ('.$functionId.')'); } @@ -294,7 +285,7 @@ class FunctionsV1 $runtimes = Config::getParam('runtimes'); Authorization::disable(); - $tag = $database->getDocument($function->getAttribute('tag', '')); + $tag = $database->getDocument('tags', $function->getAttribute('tag', '')); Authorization::reset(); if($tag->getAttribute('functionId') !== $function->getId()) { @@ -303,12 +294,9 @@ class FunctionsV1 Authorization::disable(); - $execution = (!empty($executionId)) ? $database->getDocument($executionId) : $database->createDocument([ - '$collection' => Database::SYSTEM_COLLECTION_EXECUTIONS, - '$permissions' => [ - 'read' => [], - 'write' => [], - ], + $execution = (!empty($executionId)) ? $database->getDocument('executions', $executionId) : $database->createDocument('executions', new Document([ + '$read' => [], + '$write' => [], 'dateCreated' => time(), 'functionId' => $function->getId(), 'trigger' => $trigger, // http / schedule / event @@ -317,7 +305,7 @@ class FunctionsV1 'stdout' => '', 'stderr' => '', 'time' => 0, - ]); + ])); if(false === $execution || ($execution instanceof Document && $execution->isEmpty())) { throw new Exception('Failed to create or read execution'); @@ -459,14 +447,14 @@ class FunctionsV1 Authorization::disable(); - $execution = $database->updateDocument(array_merge($execution->getArrayCopy(), [ + $execution = $database->updateDocument('executions', $execution->getId(), new Document(array_merge($execution->getArrayCopy(), [ 'tagId' => $tag->getId(), 'status' => $functionStatus, 'exitCode' => $exitCode, - 'stdout' => \mb_substr($stdout, -4000), // log last 4000 chars output - 'stderr' => \mb_substr($stderr, -4000), // log last 4000 chars output - 'time' => $executionTime, - ])); + 'stdout' => \mb_substr($stdout, -8000), // log last 4000 chars output + 'stderr' => \mb_substr($stderr, -8000), // log last 4000 chars output + 'time' => (float)$executionTime, + ]))); Authorization::reset(); From 2281970ab9efcc08320112ea7da7f8d093ea2406 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Wed, 5 May 2021 01:12:16 +0300 Subject: [PATCH 10/60] Remove leftovers --- tests/resources/docker/.env | 3 - tests/resources/docker/docker-compose.yml | 417 ---------------------- tests/resources/docker/mariadb/Dockerfile | 8 - tests/resources/docker/mariadb/all.sql | 83 ----- 4 files changed, 511 deletions(-) delete mode 100644 tests/resources/docker/.env delete mode 100644 tests/resources/docker/docker-compose.yml delete mode 100644 tests/resources/docker/mariadb/Dockerfile delete mode 100644 tests/resources/docker/mariadb/all.sql diff --git a/tests/resources/docker/.env b/tests/resources/docker/.env deleted file mode 100644 index 1c1359f0fe..0000000000 --- a/tests/resources/docker/.env +++ /dev/null @@ -1,3 +0,0 @@ -_APP_X=value1 -_APP_Y=value2 -_APP_Z = value3 \ No newline at end of file diff --git a/tests/resources/docker/docker-compose.yml b/tests/resources/docker/docker-compose.yml deleted file mode 100644 index 7a931600c2..0000000000 --- a/tests/resources/docker/docker-compose.yml +++ /dev/null @@ -1,417 +0,0 @@ -version: '3' - -services: - traefik: - image: traefik:2.2 - container_name: appwrite-traefik - command: - - --log.level=DEBUG - - --api.insecure=true - - --providers.file.directory=/storage/config - - --providers.file.watch=true - - --providers.docker=true - - --entrypoints.web.address=:80 - - --entrypoints.websecure.address=:443 - - --accesslog=true - restart: unless-stopped - ports: - - 2080:80 - - 2443:443 - - 8080:8080 - volumes: - - /var/run/docker.sock:/var/run/docker.sock - - appwrite-config:/storage/config:ro - - appwrite-certificates:/storage/certificates:ro - depends_on: - - appwrite - networks: - - gateway - - appwrite - - appwrite: - container_name: appwrite - build: - context: . - args: - - TESTING=true - - VERSION=dev - restart: unless-stopped - ports: - - 9501:80 - networks: - - appwrite - labels: - - traefik.http.routers.appwrite.rule=PathPrefix(`/`) - - 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 - - appwrite-certificates:/storage/certificates:rw - - appwrite-functions:/storage/functions:rw - - ./phpunit.xml:/usr/src/code/phpunit.xml - - ./tests:/usr/src/code/tests - - ./app:/usr/src/code/app - # - ./vendor:/usr/src/code/vendor - - ./docs:/usr/src/code/docs - - ./public:/usr/src/code/public - - ./src:/usr/src/code/src - - ./debug:/tmp - depends_on: - - mariadb - - redis - # - clamav - - influxdb - environment: - - _APP_ENV - - _APP_OPTIONS_ABUSE - - _APP_OPTIONS_FORCE_HTTPS - - _APP_OPENSSL_KEY_V1 - - _APP_DOMAIN - - _APP_DOMAIN_TARGET - - _APP_REDIS_HOST - - _APP_REDIS_PORT - - _APP_DB_HOST - - _APP_DB_PORT - - _APP_DB_SCHEMA - - _APP_DB_USER - - _APP_DB_PASS - - _APP_USAGE_STATS - - _APP_INFLUXDB_HOST - - _APP_INFLUXDB_PORT - - _APP_STORAGE_ANTIVIRUS=disabled - - _APP_STORAGE_LIMIT - - _APP_FUNCTIONS_TIMEOUT - - _APP_FUNCTIONS_CONTAINERS - - _APP_FUNCTIONS_CPUS - - _APP_FUNCTIONS_MEMORY - - _APP_FUNCTIONS_MEMORY_SWAP - - appwrite-worker-usage: - entrypoint: worker-usage - container_name: appwrite-worker-usage - build: - context: . - restart: unless-stopped - networks: - - appwrite - depends_on: - - redis - - telegraf - environment: - - _APP_ENV - - _APP_REDIS_HOST - - _APP_REDIS_PORT - - _APP_STATSD_HOST - - _APP_STATSD_PORT - - appwrite-worker-audits: - entrypoint: worker-audits - container_name: appwrite-worker-audits - build: - context: . - restart: unless-stopped - networks: - - appwrite - depends_on: - - redis - - mariadb - environment: - - _APP_ENV - - _APP_REDIS_HOST - - _APP_REDIS_PORT - - _APP_DB_HOST - - _APP_DB_PORT - - _APP_DB_SCHEMA - - _APP_DB_USER - - _APP_DB_PASS - - appwrite-worker-webhooks: - entrypoint: worker-webhooks - container_name: appwrite-worker-webhooks - build: - context: . - restart: unless-stopped - networks: - - appwrite - depends_on: - - redis - - mariadb - environment: - - _APP_ENV - - _APP_REDIS_HOST - - _APP_REDIS_PORT - - _APP_DB_HOST - - _APP_DB_PORT - - _APP_DB_SCHEMA - - _APP_DB_USER - - _APP_DB_PASS - - appwrite-worker-tasks: - entrypoint: worker-tasks - container_name: appwrite-worker-tasks - build: - context: . - restart: unless-stopped - networks: - - appwrite - depends_on: - - redis - - mariadb - environment: - - _APP_ENV - - _APP_REDIS_HOST - - _APP_REDIS_PORT - - _APP_DB_HOST - - _APP_DB_PORT - - _APP_DB_SCHEMA - - _APP_DB_USER - - _APP_DB_PASS - - appwrite-worker-deletes: - entrypoint: worker-deletes - container_name: appwrite-worker-deletes - build: - context: . - restart: unless-stopped - networks: - - appwrite - depends_on: - - redis - - mariadb - volumes: - - appwrite-uploads:/storage/uploads:rw - - appwrite-cache:/storage/cache:rw - environment: - - _APP_ENV - - _APP_REDIS_HOST - - _APP_REDIS_PORT - - _APP_DB_HOST - - _APP_DB_PORT - - _APP_DB_SCHEMA - - _APP_DB_USER - - _APP_DB_PASS - - appwrite-worker-certificates: - entrypoint: worker-certificates - container_name: appwrite-worker-certificates - build: - context: . - restart: unless-stopped - networks: - - appwrite - depends_on: - - redis - - mariadb - volumes: - - appwrite-config:/storage/config:rw - - appwrite-certificates:/storage/certificates:rw - environment: - - _APP_ENV - - _APP_REDIS_HOST - - _APP_REDIS_PORT - - _APP_DB_HOST - - _APP_DB_PORT - - _APP_DB_SCHEMA - - _APP_DB_USER - - _APP_DB_PASS - - appwrite-worker-functions: - entrypoint: worker-functions - container_name: appwrite-worker-functions - build: - context: . - restart: unless-stopped - networks: - - appwrite - depends_on: - - redis - - mariadb - volumes: - - /var/run/docker.sock:/var/run/docker.sock - - /tmp:/tmp:rw - - appwrite-functions:/storage/functions:rw - environment: - - _APP_ENV - - _APP_REDIS_HOST - - _APP_REDIS_PORT - - _APP_DB_HOST - - _APP_DB_PORT - - _APP_DB_SCHEMA - - _APP_DB_USER - - _APP_DB_PASS - - _APP_FUNCTIONS_TIMEOUT - - _APP_FUNCTIONS_CONTAINERS - - _APP_FUNCTIONS_CPUS - - _APP_FUNCTIONS_MEMORY - - _APP_FUNCTIONS_MEMORY_SWAP - - appwrite-worker-mails: - entrypoint: worker-mails - container_name: appwrite-worker-mails - build: - context: . - restart: unless-stopped - networks: - - appwrite - depends_on: - - redis - - maildev - # - smtp - environment: - - _APP_ENV - - _APP_REDIS_HOST - - _APP_REDIS_PORT - - _APP_SMTP_HOST - - _APP_SMTP_PORT - - appwrite-schedule: - entrypoint: schedule - container_name: appwrite-schedule - build: - context: . - restart: unless-stopped - networks: - - appwrite - depends_on: - - redis - environment: - - _APP_ENV - - _APP_REDIS_HOST - - _APP_REDIS_PORT - - mariadb: - image: appwrite/mariadb:1.2.0 # fix issues when upgrading using: mysql_upgrade -u root -p - container_name: appwrite-mariadb - restart: unless-stopped - networks: - - appwrite - volumes: - - appwrite-mariadb:/var/lib/mysql:rw - ports: - - "3306:3306" - environment: - - MYSQL_ROOT_PASSWORD=rootsecretpassword - - MYSQL_DATABASE=appwrite - - MYSQL_USER=user - - MYSQL_PASSWORD=password - command: 'mysqld --innodb-flush-method=fsync' - - maildev: - image: djfarrelly/maildev - container_name: appwrite-maildev - restart: unless-stopped - ports: - - '1080:80' - networks: - - appwrite - - # smtp: - # image: appwrite/smtp:1.0.1 - # container_name: appwrite-smtp - # restart: unless-stopped - # networks: - # - appwrite - # environment: - # - MAILNAME=appwrite - # - RELAY_NETWORKS=:192.168.0.0/24:10.0.0.0/16 - - redis: - image: redis:5.0 - container_name: appwrite-redis - restart: unless-stopped - networks: - - appwrite - volumes: - - appwrite-redis:/data:rw - - # clamav: - # image: appwrite/clamav:1.2.0 - # container_name: appwrite-clamav - # restart: unless-stopped - # networks: - # - appwrite - # volumes: - # - appwrite-uploads:/storage/uploads - - influxdb: - image: influxdb:1.6 - container_name: appwrite-influxdb - restart: unless-stopped - networks: - - appwrite - volumes: - - appwrite-influxdb:/var/lib/influxdb:rw - - telegraf: - image: appwrite/telegraf:1.0.0 - container_name: appwrite-telegraf - restart: unless-stopped - networks: - - appwrite - - # redis-commander: - # image: rediscommander/redis-commander:latest - # restart: unless-stopped - # networks: - # - appwrite - # environment: - # - REDIS_HOSTS=redis - # ports: - # - "8081:8081" - - # resque: - # image: registry.gitlab.com/appwrite/appwrite/resque-web:v1.0.2 - # restart: unless-stopped - # networks: - # - appwrite - # ports: - # - "5678:5678" - # environment: - # - RESQUE_WEB_HOST=redis - # - RESQUE_WEB_PORT=6379 - # - RESQUE_WEB_HTTP_BASIC_AUTH_USER=user - # - RESQUE_WEB_HTTP_BASIC_AUTH_PASSWORD=password - - # chronograf: - # image: chronograf:1.5 - # container_name: appwrite-chronograf - # restart: unless-stopped - # networks: - # - appwrite - # volumes: - # - appwrite-chronograf:/var/lib/chronograf - # ports: - # - "8888:8888" - # environment: - # - INFLUXDB_URL=http://influxdb:8086 - # - KAPACITOR_URL=http://kapacitor:9092 - # - AUTH_DURATION=48h - # - TOKEN_SECRET=duperduper5674829!jwt - # - GH_CLIENT_ID=d86f7145a41eacfc52cc - # - GH_CLIENT_SECRET=9e0081062367a2134e7f2ea95ba1a32d08b6c8ab - # - GH_ORGS=appwrite - - # webgrind: - # image: 'jokkedk/webgrind:latest' - # volumes: - # - './debug:/tmp' - # ports: - # - '3001:80' - -networks: - gateway: - appwrite: - -volumes: - appwrite-mariadb: - appwrite-redis: - appwrite-cache: - appwrite-uploads: - appwrite-certificates: - appwrite-functions: - appwrite-influxdb: - appwrite-chronograf: - appwrite-config: diff --git a/tests/resources/docker/mariadb/Dockerfile b/tests/resources/docker/mariadb/Dockerfile deleted file mode 100644 index 09ed2ec066..0000000000 --- a/tests/resources/docker/mariadb/Dockerfile +++ /dev/null @@ -1,8 +0,0 @@ -FROM mariadb:10.2 - -LABEL maintainer="team@appwrite.io" - -# Add appwrite schema and tables -ADD ./all.sql /docker-entrypoint-initdb.d/all.sql - -RUN chown -R mysql:mysql /docker-entrypoint-initdb.d/ \ No newline at end of file diff --git a/tests/resources/docker/mariadb/all.sql b/tests/resources/docker/mariadb/all.sql deleted file mode 100644 index 487044cbd6..0000000000 --- a/tests/resources/docker/mariadb/all.sql +++ /dev/null @@ -1,83 +0,0 @@ -CREATE DATABASE IF NOT EXISTS `appwrite` /*!40100 DEFAULT CHARACTER SET utf8mb4 */; - -USE `appwrite`; - -CREATE TABLE IF NOT EXISTS `template.abuse.abuse` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `_key` varchar(255) NOT NULL, - `_time` varchar(45) NOT NULL, - `_count` int(11) NOT NULL DEFAULT '0', - PRIMARY KEY (`id`), - UNIQUE KEY `unique1` (`_key`,`_time`), - KEY `index1` (`_key`,`_time`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; - -CREATE TABLE IF NOT EXISTS `template.audit.audit` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `userId` varchar(45) NOT NULL, - `userType` int(11) NOT NULL, - `event` varchar(45) NOT NULL, - `resource` varchar(45) DEFAULT NULL, - `userAgent` text NOT NULL, - `ip` varchar(45) NOT NULL, - `location` varchar(45) DEFAULT NULL, - `time` datetime NOT NULL, - `data` longtext DEFAULT NULL, - PRIMARY KEY (`id`), - UNIQUE KEY `id_UNIQUE` (`id`), - KEY `index_1` (`userId`,`userType`), - KEY `index_2` (`event`), - KEY `index_3` (`resource`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; - -CREATE TABLE IF NOT EXISTS `template.database.documents` ( - `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'Unique ID for each node', - `uid` varchar(45) DEFAULT NULL, - `status` int(11) NOT NULL DEFAULT 0, - `createdAt` datetime DEFAULT NULL, - `updatedAt` datetime DEFAULT NULL, - `signature` varchar(32) NOT NULL, - `revision` varchar(45) NOT NULL, - `permissions` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL, - PRIMARY KEY (`id`), - UNIQUE KEY `id_UNIQUE` (`id`), - UNIQUE KEY `index2` (`uid`), - KEY `index3` (`signature`,`uid`,`revision`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; - -CREATE TABLE IF NOT EXISTS `template.database.properties` ( - `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'Primary Key', - `documentUid` varchar(45) NOT NULL COMMENT 'Unique UID foreign key', - `documentRevision` varchar(45) NOT NULL, - `key` varchar(32) NOT NULL COMMENT 'Property key name', - `value` text NOT NULL COMMENT 'Value of property', - `primitive` varchar(32) NOT NULL COMMENT 'Primitive type of property value', - `array` tinyint(4) NOT NULL DEFAULT 0, - `order` int(11) NOT NULL, - PRIMARY KEY (`id`), - KEY `index1` (`documentUid`), - KEY `index2` (`key`,`value`(5)), - FULLTEXT KEY `index3` (`value`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; - -CREATE TABLE IF NOT EXISTS `template.database.relationships` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `revision` varchar(45) NOT NULL, - `start` varchar(45) NOT NULL COMMENT 'Unique UID foreign key', - `end` varchar(45) NOT NULL COMMENT 'Unique UID foreign key', - `key` varchar(256) NOT NULL, - `path` int(11) NOT NULL DEFAULT 0, - `array` tinyint(4) NOT NULL DEFAULT 0, - `order` int(11) NOT NULL DEFAULT 0, - PRIMARY KEY (`id`), - KEY `relationships_start_nodes_id_idx` (`start`), - KEY `relationships_end_nodes_id_idx` (`end`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; - -/* Default App */ - -CREATE TABLE IF NOT EXISTS `app_console.database.documents` LIKE `template.database.documents`; -CREATE TABLE IF NOT EXISTS `app_console.database.properties` LIKE `template.database.properties`; -CREATE TABLE IF NOT EXISTS `app_console.database.relationships` LIKE `template.database.relationships`; -CREATE TABLE IF NOT EXISTS `app_console.audit.audit` LIKE `template.audit.audit`; -CREATE TABLE IF NOT EXISTS `app_console.abuse.abuse` LIKE `template.abuse.abuse`; \ No newline at end of file From 5de0848797c37518e808ba5c478053bd203e2c1f Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Wed, 5 May 2021 01:16:25 +0300 Subject: [PATCH 11/60] Test CI --- .travis.yml | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2265373c23..a48d630e4b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -34,17 +34,18 @@ install: - sleep 10 script: -- docker ps +- docker ps -a - docker-compose logs appwrite -- docker-compose logs appwrite-worker-functions -- docker-compose exec appwrite doctor -- docker-compose exec appwrite vars -- docker-compose exec appwrite test --debug +- docker-compose logs mariadb +# - docker-compose logs appwrite-worker-functions +# - docker-compose exec appwrite doctor +# - docker-compose exec appwrite vars +# - docker-compose exec appwrite test --debug -deploy: - - provider: script - edge: true - script: ./.travis-ci/deploy.sh - on: - repo: appwrite/appwrite - branch: deploy +# deploy: +# - provider: script +# edge: true +# script: ./.travis-ci/deploy.sh +# on: +# repo: appwrite/appwrite +# branch: deploy From 1906205f6fe61aaf59a2ece2da0358d427598783 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Wed, 5 May 2021 01:38:09 +0300 Subject: [PATCH 12/60] Fix tests --- .travis.yml | 22 +++++++++++----------- docker-compose.yml | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.travis.yml b/.travis.yml index a48d630e4b..f967bb2488 100644 --- a/.travis.yml +++ b/.travis.yml @@ -37,15 +37,15 @@ script: - docker ps -a - docker-compose logs appwrite - docker-compose logs mariadb -# - docker-compose logs appwrite-worker-functions -# - docker-compose exec appwrite doctor -# - docker-compose exec appwrite vars -# - docker-compose exec appwrite test --debug +- docker-compose logs appwrite-worker-functions +- docker-compose exec appwrite doctor +- docker-compose exec appwrite vars +- docker-compose exec appwrite test --debug -# deploy: -# - provider: script -# edge: true -# script: ./.travis-ci/deploy.sh -# on: -# repo: appwrite/appwrite -# branch: deploy +deploy: + - provider: script + edge: true + script: ./.travis-ci/deploy.sh + on: + repo: appwrite/appwrite + branch: deploy diff --git a/docker-compose.yml b/docker-compose.yml index 3104eb4bf0..bdd7f59966 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -389,7 +389,7 @@ services: environment: - MYSQL_ROOT_PASSWORD=password - MYSQL_DATABASE=${_APP_DB_SCHEMA} - - MYSQL_USER=${_APP_DB_USER} + - MYSQL_USER=user - MYSQL_PASSWORD=${_APP_DB_PASS} command: 'mysqld --innodb-flush-method=fsync' # add ' --query_cache_size=0' for DB tests # command: mv /var/lib/mysql/ib_logfile0 /var/lib/mysql/ib_logfile0.bu && mv /var/lib/mysql/ib_logfile1 /var/lib/mysql/ib_logfile1.bu From 4e67339e684c0925d98c9a4132456511a39fa9f7 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Wed, 5 May 2021 08:57:53 +0300 Subject: [PATCH 13/60] Reverted test files --- tests/resources/docker/.env | 3 + tests/resources/docker/docker-compose.yml | 417 ++++++++++++++++++++++ 2 files changed, 420 insertions(+) create mode 100644 tests/resources/docker/.env create mode 100644 tests/resources/docker/docker-compose.yml diff --git a/tests/resources/docker/.env b/tests/resources/docker/.env new file mode 100644 index 0000000000..1c1359f0fe --- /dev/null +++ b/tests/resources/docker/.env @@ -0,0 +1,3 @@ +_APP_X=value1 +_APP_Y=value2 +_APP_Z = value3 \ No newline at end of file diff --git a/tests/resources/docker/docker-compose.yml b/tests/resources/docker/docker-compose.yml new file mode 100644 index 0000000000..7a931600c2 --- /dev/null +++ b/tests/resources/docker/docker-compose.yml @@ -0,0 +1,417 @@ +version: '3' + +services: + traefik: + image: traefik:2.2 + container_name: appwrite-traefik + command: + - --log.level=DEBUG + - --api.insecure=true + - --providers.file.directory=/storage/config + - --providers.file.watch=true + - --providers.docker=true + - --entrypoints.web.address=:80 + - --entrypoints.websecure.address=:443 + - --accesslog=true + restart: unless-stopped + ports: + - 2080:80 + - 2443:443 + - 8080:8080 + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - appwrite-config:/storage/config:ro + - appwrite-certificates:/storage/certificates:ro + depends_on: + - appwrite + networks: + - gateway + - appwrite + + appwrite: + container_name: appwrite + build: + context: . + args: + - TESTING=true + - VERSION=dev + restart: unless-stopped + ports: + - 9501:80 + networks: + - appwrite + labels: + - traefik.http.routers.appwrite.rule=PathPrefix(`/`) + - 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 + - appwrite-certificates:/storage/certificates:rw + - appwrite-functions:/storage/functions:rw + - ./phpunit.xml:/usr/src/code/phpunit.xml + - ./tests:/usr/src/code/tests + - ./app:/usr/src/code/app + # - ./vendor:/usr/src/code/vendor + - ./docs:/usr/src/code/docs + - ./public:/usr/src/code/public + - ./src:/usr/src/code/src + - ./debug:/tmp + depends_on: + - mariadb + - redis + # - clamav + - influxdb + environment: + - _APP_ENV + - _APP_OPTIONS_ABUSE + - _APP_OPTIONS_FORCE_HTTPS + - _APP_OPENSSL_KEY_V1 + - _APP_DOMAIN + - _APP_DOMAIN_TARGET + - _APP_REDIS_HOST + - _APP_REDIS_PORT + - _APP_DB_HOST + - _APP_DB_PORT + - _APP_DB_SCHEMA + - _APP_DB_USER + - _APP_DB_PASS + - _APP_USAGE_STATS + - _APP_INFLUXDB_HOST + - _APP_INFLUXDB_PORT + - _APP_STORAGE_ANTIVIRUS=disabled + - _APP_STORAGE_LIMIT + - _APP_FUNCTIONS_TIMEOUT + - _APP_FUNCTIONS_CONTAINERS + - _APP_FUNCTIONS_CPUS + - _APP_FUNCTIONS_MEMORY + - _APP_FUNCTIONS_MEMORY_SWAP + + appwrite-worker-usage: + entrypoint: worker-usage + container_name: appwrite-worker-usage + build: + context: . + restart: unless-stopped + networks: + - appwrite + depends_on: + - redis + - telegraf + environment: + - _APP_ENV + - _APP_REDIS_HOST + - _APP_REDIS_PORT + - _APP_STATSD_HOST + - _APP_STATSD_PORT + + appwrite-worker-audits: + entrypoint: worker-audits + container_name: appwrite-worker-audits + build: + context: . + restart: unless-stopped + networks: + - appwrite + depends_on: + - redis + - mariadb + environment: + - _APP_ENV + - _APP_REDIS_HOST + - _APP_REDIS_PORT + - _APP_DB_HOST + - _APP_DB_PORT + - _APP_DB_SCHEMA + - _APP_DB_USER + - _APP_DB_PASS + + appwrite-worker-webhooks: + entrypoint: worker-webhooks + container_name: appwrite-worker-webhooks + build: + context: . + restart: unless-stopped + networks: + - appwrite + depends_on: + - redis + - mariadb + environment: + - _APP_ENV + - _APP_REDIS_HOST + - _APP_REDIS_PORT + - _APP_DB_HOST + - _APP_DB_PORT + - _APP_DB_SCHEMA + - _APP_DB_USER + - _APP_DB_PASS + + appwrite-worker-tasks: + entrypoint: worker-tasks + container_name: appwrite-worker-tasks + build: + context: . + restart: unless-stopped + networks: + - appwrite + depends_on: + - redis + - mariadb + environment: + - _APP_ENV + - _APP_REDIS_HOST + - _APP_REDIS_PORT + - _APP_DB_HOST + - _APP_DB_PORT + - _APP_DB_SCHEMA + - _APP_DB_USER + - _APP_DB_PASS + + appwrite-worker-deletes: + entrypoint: worker-deletes + container_name: appwrite-worker-deletes + build: + context: . + restart: unless-stopped + networks: + - appwrite + depends_on: + - redis + - mariadb + volumes: + - appwrite-uploads:/storage/uploads:rw + - appwrite-cache:/storage/cache:rw + environment: + - _APP_ENV + - _APP_REDIS_HOST + - _APP_REDIS_PORT + - _APP_DB_HOST + - _APP_DB_PORT + - _APP_DB_SCHEMA + - _APP_DB_USER + - _APP_DB_PASS + + appwrite-worker-certificates: + entrypoint: worker-certificates + container_name: appwrite-worker-certificates + build: + context: . + restart: unless-stopped + networks: + - appwrite + depends_on: + - redis + - mariadb + volumes: + - appwrite-config:/storage/config:rw + - appwrite-certificates:/storage/certificates:rw + environment: + - _APP_ENV + - _APP_REDIS_HOST + - _APP_REDIS_PORT + - _APP_DB_HOST + - _APP_DB_PORT + - _APP_DB_SCHEMA + - _APP_DB_USER + - _APP_DB_PASS + + appwrite-worker-functions: + entrypoint: worker-functions + container_name: appwrite-worker-functions + build: + context: . + restart: unless-stopped + networks: + - appwrite + depends_on: + - redis + - mariadb + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - /tmp:/tmp:rw + - appwrite-functions:/storage/functions:rw + environment: + - _APP_ENV + - _APP_REDIS_HOST + - _APP_REDIS_PORT + - _APP_DB_HOST + - _APP_DB_PORT + - _APP_DB_SCHEMA + - _APP_DB_USER + - _APP_DB_PASS + - _APP_FUNCTIONS_TIMEOUT + - _APP_FUNCTIONS_CONTAINERS + - _APP_FUNCTIONS_CPUS + - _APP_FUNCTIONS_MEMORY + - _APP_FUNCTIONS_MEMORY_SWAP + + appwrite-worker-mails: + entrypoint: worker-mails + container_name: appwrite-worker-mails + build: + context: . + restart: unless-stopped + networks: + - appwrite + depends_on: + - redis + - maildev + # - smtp + environment: + - _APP_ENV + - _APP_REDIS_HOST + - _APP_REDIS_PORT + - _APP_SMTP_HOST + - _APP_SMTP_PORT + + appwrite-schedule: + entrypoint: schedule + container_name: appwrite-schedule + build: + context: . + restart: unless-stopped + networks: + - appwrite + depends_on: + - redis + environment: + - _APP_ENV + - _APP_REDIS_HOST + - _APP_REDIS_PORT + + mariadb: + image: appwrite/mariadb:1.2.0 # fix issues when upgrading using: mysql_upgrade -u root -p + container_name: appwrite-mariadb + restart: unless-stopped + networks: + - appwrite + volumes: + - appwrite-mariadb:/var/lib/mysql:rw + ports: + - "3306:3306" + environment: + - MYSQL_ROOT_PASSWORD=rootsecretpassword + - MYSQL_DATABASE=appwrite + - MYSQL_USER=user + - MYSQL_PASSWORD=password + command: 'mysqld --innodb-flush-method=fsync' + + maildev: + image: djfarrelly/maildev + container_name: appwrite-maildev + restart: unless-stopped + ports: + - '1080:80' + networks: + - appwrite + + # smtp: + # image: appwrite/smtp:1.0.1 + # container_name: appwrite-smtp + # restart: unless-stopped + # networks: + # - appwrite + # environment: + # - MAILNAME=appwrite + # - RELAY_NETWORKS=:192.168.0.0/24:10.0.0.0/16 + + redis: + image: redis:5.0 + container_name: appwrite-redis + restart: unless-stopped + networks: + - appwrite + volumes: + - appwrite-redis:/data:rw + + # clamav: + # image: appwrite/clamav:1.2.0 + # container_name: appwrite-clamav + # restart: unless-stopped + # networks: + # - appwrite + # volumes: + # - appwrite-uploads:/storage/uploads + + influxdb: + image: influxdb:1.6 + container_name: appwrite-influxdb + restart: unless-stopped + networks: + - appwrite + volumes: + - appwrite-influxdb:/var/lib/influxdb:rw + + telegraf: + image: appwrite/telegraf:1.0.0 + container_name: appwrite-telegraf + restart: unless-stopped + networks: + - appwrite + + # redis-commander: + # image: rediscommander/redis-commander:latest + # restart: unless-stopped + # networks: + # - appwrite + # environment: + # - REDIS_HOSTS=redis + # ports: + # - "8081:8081" + + # resque: + # image: registry.gitlab.com/appwrite/appwrite/resque-web:v1.0.2 + # restart: unless-stopped + # networks: + # - appwrite + # ports: + # - "5678:5678" + # environment: + # - RESQUE_WEB_HOST=redis + # - RESQUE_WEB_PORT=6379 + # - RESQUE_WEB_HTTP_BASIC_AUTH_USER=user + # - RESQUE_WEB_HTTP_BASIC_AUTH_PASSWORD=password + + # chronograf: + # image: chronograf:1.5 + # container_name: appwrite-chronograf + # restart: unless-stopped + # networks: + # - appwrite + # volumes: + # - appwrite-chronograf:/var/lib/chronograf + # ports: + # - "8888:8888" + # environment: + # - INFLUXDB_URL=http://influxdb:8086 + # - KAPACITOR_URL=http://kapacitor:9092 + # - AUTH_DURATION=48h + # - TOKEN_SECRET=duperduper5674829!jwt + # - GH_CLIENT_ID=d86f7145a41eacfc52cc + # - GH_CLIENT_SECRET=9e0081062367a2134e7f2ea95ba1a32d08b6c8ab + # - GH_ORGS=appwrite + + # webgrind: + # image: 'jokkedk/webgrind:latest' + # volumes: + # - './debug:/tmp' + # ports: + # - '3001:80' + +networks: + gateway: + appwrite: + +volumes: + appwrite-mariadb: + appwrite-redis: + appwrite-cache: + appwrite-uploads: + appwrite-certificates: + appwrite-functions: + appwrite-influxdb: + appwrite-chronograf: + appwrite-config: From f69c95e11920e2edc8010e462a93b20a136e015b Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Wed, 5 May 2021 10:32:20 +0300 Subject: [PATCH 14/60] Fixed tests --- app/init.php | 2 +- tests/e2e/Services/Webhooks/WebhooksBase.php | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/init.php b/app/init.php index 2990fc6276..7a163a39cb 100644 --- a/app/init.php +++ b/app/init.php @@ -552,7 +552,7 @@ App::setResource('dbForConsole', function($register) { $cache = new Cache(new RedisCache($register->get('cache'))); $database = new Database2(new MariaDB($register->get('db')), $cache); - $database->setNamespace('project_console'); + $database->setNamespace('project_console_internal'); return $database; }, ['register']); diff --git a/tests/e2e/Services/Webhooks/WebhooksBase.php b/tests/e2e/Services/Webhooks/WebhooksBase.php index 5dc6cfafe2..a687e2d445 100644 --- a/tests/e2e/Services/Webhooks/WebhooksBase.php +++ b/tests/e2e/Services/Webhooks/WebhooksBase.php @@ -228,7 +228,8 @@ trait WebhooksBase $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-User-Id'] ?? ''), ('server' === $this->getSide())); $this->assertNotEmpty($webhook['data']['$id']); - $this->assertIsArray($webhook['data']['$permissions']); + $this->assertIsArray($webhook['data']['$read']); + $this->assertIsArray($webhook['data']['$write']); $this->assertEquals($webhook['data']['name'], 'logo.png'); $this->assertIsInt($webhook['data']['dateCreated'], 'logo.png'); $this->assertNotEmpty($webhook['data']['signature']); From aec1dea7b4230909b3844a31ade9de26988304ec Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Wed, 5 May 2021 13:00:06 +0300 Subject: [PATCH 15/60] Updated permissions --- tests/e2e/Services/Webhooks/WebhooksBase.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/e2e/Services/Webhooks/WebhooksBase.php b/tests/e2e/Services/Webhooks/WebhooksBase.php index a687e2d445..40f2ae32c1 100644 --- a/tests/e2e/Services/Webhooks/WebhooksBase.php +++ b/tests/e2e/Services/Webhooks/WebhooksBase.php @@ -272,7 +272,8 @@ trait WebhooksBase $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-User-Id'] ?? ''), ('server' === $this->getSide())); $this->assertNotEmpty($webhook['data']['$id']); - $this->assertIsArray($webhook['data']['$permissions']); + $this->assertIsArray($webhook['data']['$read']); + $this->assertIsArray($webhook['data']['$write']); $this->assertEquals($webhook['data']['name'], 'logo.png'); $this->assertIsInt($webhook['data']['dateCreated'], 'logo.png'); $this->assertNotEmpty($webhook['data']['signature']); @@ -309,7 +310,8 @@ trait WebhooksBase $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-User-Id'] ?? ''), ('server' === $this->getSide())); $this->assertNotEmpty($webhook['data']['$id']); - $this->assertIsArray($webhook['data']['$permissions']); + $this->assertIsArray($webhook['data']['$read']); + $this->assertIsArray($webhook['data']['$write']); $this->assertEquals($webhook['data']['name'], 'logo.png'); $this->assertIsInt($webhook['data']['dateCreated'], 'logo.png'); $this->assertNotEmpty($webhook['data']['signature']); From a128ad08b575333110fddba044560f7a23a932b4 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Wed, 5 May 2021 15:28:01 +0300 Subject: [PATCH 16/60] Added indexes --- app/config/collections2.php | 174 ++++++++++++++++++++++++++++++- app/controllers/api/projects.php | 9 +- app/http.php | 12 ++- 3 files changed, 187 insertions(+), 8 deletions(-) diff --git a/app/config/collections2.php b/app/config/collections2.php index b29c8b505c..077a4a763c 100644 --- a/app/config/collections2.php +++ b/app/config/collections2.php @@ -135,8 +135,149 @@ $collections = [ ], ], + 'indexes' => [ + [ + '$id' => '_key_email', + 'type' => Database::INDEX_UNIQUE, + 'attributes' => ['email'], + 'lengths' => [1024], + 'orders' => [Database::ORDER_ASC], + ] + ], + ], + + 'teams' => [ + '$collection' => Database::COLLECTIONS, + '$id' => 'teams', + 'name' => 'Teams', + 'attributes' => [ + [ + '$id' => 'name', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 128, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'dateCreated', + 'type' => Database::VAR_INTEGER, + 'format' => '', + 'size' => 0, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'sum', + 'type' => Database::VAR_INTEGER, + 'format' => '', + 'size' => 0, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + ], 'indexes' => [], ], + + 'memberships' => [ + '$collection' => Database::COLLECTIONS, + '$id' => 'memberships', + 'name' => 'Memberships', + 'attributes' => [ + [ + '$id' => 'teamId', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'userId', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'roles', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 128, + 'signed' => true, + 'required' => false, + 'array' => true, + 'filters' => [], + ], + [ + '$id' => 'invited', + 'type' => Database::VAR_INTEGER, + 'format' => '', + 'size' => 0, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'joined', + 'type' => Database::VAR_INTEGER, + 'format' => '', + 'size' => 0, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'confirm', + 'type' => Database::VAR_BOOLEAN, + 'format' => '', + 'size' => 0, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'secret', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 256, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + ], + 'indexes' => [ + [ + '$id' => '_key_team', + 'type' => Database::INDEX_UNIQUE, + 'attributes' => ['teamId'], + 'lengths' => [Database::LENGTH_KEY], + 'orders' => [Database::ORDER_ASC], + ], + [ + '$id' => '_key_user', + 'type' => Database::INDEX_UNIQUE, + 'attributes' => ['userId'], + 'lengths' => [Database::LENGTH_KEY], + 'orders' => [Database::ORDER_ASC], + ] + ], + ], 'files' => [ '$collection' => Database::COLLECTIONS, @@ -285,7 +426,15 @@ $collections = [ 'filters' => [], ], ], - 'indexes' => [], + 'indexes' => [ + [ + '$id' => '_key_bucket', + 'type' => Database::INDEX_UNIQUE, + 'attributes' => ['bucketId'], + 'lengths' => [Database::LENGTH_KEY], + 'orders' => [Database::ORDER_ASC], + ] + ], ], 'functions' => [ @@ -425,7 +574,8 @@ $collections = [ 'filters' => [], ], ], - 'indexes' => [], + 'indexes' => [ + ], ], 'tags' => [ @@ -485,7 +635,15 @@ $collections = [ 'filters' => [], ], ], - 'indexes' => [], + 'indexes' => [ + [ + '$id' => '_key_function', + 'type' => Database::INDEX_UNIQUE, + 'attributes' => ['functionId'], + 'lengths' => [Database::LENGTH_KEY], + 'orders' => [Database::ORDER_ASC], + ] + ], ], 'executions' => [ @@ -585,7 +743,15 @@ $collections = [ 'filters' => [], ], ], - 'indexes' => [], + 'indexes' => [ + [ + '$id' => '_key_function', + 'type' => Database::INDEX_UNIQUE, + 'attributes' => ['functionId'], + 'lengths' => [Database::LENGTH_KEY], + 'orders' => [Database::ORDER_ASC], + ] + ], ], ]; diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index c3ecb5e532..ff845bd674 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -116,7 +116,14 @@ App::post('/v1/projects') } foreach ($collection['indexes'] as $i => $index) { - + $dbForInternal->createIndex( + $key, + $index['$id'], + $index['type'], + $index['attributes'], + $index['lengths'], + $index['orders'], + ); } } diff --git a/app/http.php b/app/http.php index ec8fffd025..125c4af754 100644 --- a/app/http.php +++ b/app/http.php @@ -52,9 +52,8 @@ $http->on('AfterReload', function($serv, $workerId) { $http->on('start', function (Server $http) use ($payloadSize) { - /** @var Utopia\Database\Database $dbForConsole */ $app = new App('UTC'); - $dbForConsole = $app->getResource('dbForConsole'); + $dbForConsole = $app->getResource('dbForConsole'); /** @var Utopia\Database\Database $dbForConsole */ if(!$dbForConsole->exists()) { Console::success('[Setup] - Server database init started...'); @@ -80,7 +79,14 @@ $http->on('start', function (Server $http) use ($payloadSize) { } foreach ($collection['indexes'] as $i => $index) { - + $dbForConsole->createIndex( + $key, + $index['$id'], + $index['type'], + $index['attributes'], + $index['lengths'], + $index['orders'], + ); } } From 7192ead92d1194ceb5dae8a9e444a410bea87e31 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Wed, 5 May 2021 17:52:34 +0300 Subject: [PATCH 17/60] Cleanups --- app/controllers/api/functions.php | 2 +- composer.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index 84d5bc9219..425c9b1127 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -99,7 +99,7 @@ App::get('/v1/functions') $response->dynamic2(new Document([ 'sum' => $dbForInternal->count('functions', [], 5000), - 'functions' => $dbForInternal->find('functions', [], $limit, $offset, [], []) + 'functions' => $dbForInternal->find('functions', [], $limit, $offset) ]), Response::MODEL_FUNCTION_LIST); }); diff --git a/composer.lock b/composer.lock index 997d6156df..a18a54c132 100644 --- a/composer.lock +++ b/composer.lock @@ -1923,7 +1923,7 @@ "source": { "type": "git", "url": "https://github.com/utopia-php/database", - "reference": "52c8cacf1d7930c7ae4cad034666a4322e3c56b0" + "reference": "a41bbe4674c2fea3ce84942e40f7c57b4903a918" }, "require": { "ext-mongodb": "*", @@ -1970,7 +1970,7 @@ "upf", "utopia" ], - "time": "2021-05-04T21:14:11+00:00" + "time": "2021-05-05T13:43:40+00:00" }, { "name": "utopia-php/domains", From 81bf74ef0609bb6bcce90493ec03bd08c131edd1 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Fri, 7 May 2021 00:35:05 +0300 Subject: [PATCH 18/60] Reset cache on setup --- app/http.php | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/app/http.php b/app/http.php index 125c4af754..6b48cc1c89 100644 --- a/app/http.php +++ b/app/http.php @@ -50,16 +50,21 @@ $http->on('AfterReload', function($serv, $workerId) { Console::success('Reload completed...'); }); -$http->on('start', function (Server $http) use ($payloadSize) { +Files::load(__DIR__ . '/../public'); +include __DIR__ . '/controllers/general.php'; + +$http->on('start', function (Server $http) use ($payloadSize, $register) { $app = new App('UTC'); $dbForConsole = $app->getResource('dbForConsole'); /** @var Utopia\Database\Database $dbForConsole */ if(!$dbForConsole->exists()) { Console::success('[Setup] - Server database init started...'); - + $collections = Config::getParam('collections2', []); /** @var array $collections */ + $register->get('cache')->flushAll(); + $dbForConsole->create(); foreach ($collections as $key => $collection) { @@ -104,10 +109,6 @@ $http->on('start', function (Server $http) use ($payloadSize) { }); }); -Files::load(__DIR__ . '/../public'); - -include __DIR__ . '/controllers/general.php'; - $domain = App::getEnv('_APP_DOMAIN', ''); Console::info('Issuing a TLS certificate for the master domain ('.$domain.') in 30 seconds. From bd3069a60a8895748aa672d03d8d6ea038bbf37c Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Fri, 7 May 2021 00:35:57 +0300 Subject: [PATCH 19/60] Updated collections --- app/config/collections2.php | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/app/config/collections2.php b/app/config/collections2.php index 077a4a763c..74d4a57ce3 100644 --- a/app/config/collections2.php +++ b/app/config/collections2.php @@ -46,7 +46,7 @@ $collections = [ '$id' => 'password', 'type' => Database::VAR_STRING, 'format' => '', - 'size' => 32, + 'size' => 16384, 'signed' => true, 'required' => false, 'array' => false, @@ -66,12 +66,11 @@ $collections = [ '$id' => 'prefs', 'type' => Database::VAR_STRING, 'format' => '', - 'size' => 64000, + 'size' => 16384, 'signed' => true, 'required' => false, 'array' => false, 'filters' => ['json'] - ], [ '$id' => 'registration', @@ -107,7 +106,7 @@ $collections = [ '$id' => 'sessions', 'type' => Database::VAR_STRING, 'format' => '', - 'size' => 0, + 'size' => 16384, 'signed' => true, 'required' => false, 'array' => true, @@ -117,7 +116,7 @@ $collections = [ '$id' => 'tokens', 'type' => Database::VAR_STRING, 'format' => '', - 'size' => 0, + 'size' => 16384, 'signed' => true, 'required' => false, 'array' => true, @@ -127,12 +126,11 @@ $collections = [ '$id' => 'memberships', 'type' => Database::VAR_STRING, 'format' => '', - 'size' => 0, + 'size' => 16384, 'signed' => true, 'required' => false, 'array' => true, 'filters' => ['json'], - ], ], 'indexes' => [ @@ -263,15 +261,22 @@ $collections = [ ], 'indexes' => [ [ - '$id' => '_key_team', + '$id' => '_key_unique', 'type' => Database::INDEX_UNIQUE, + 'attributes' => ['teamId', 'userId'], + 'lengths' => [Database::LENGTH_KEY, Database::LENGTH_KEY], + 'orders' => [Database::ORDER_ASC, Database::ORDER_ASC], + ], + [ + '$id' => '_key_team', + 'type' => Database::INDEX_KEY, 'attributes' => ['teamId'], 'lengths' => [Database::LENGTH_KEY], 'orders' => [Database::ORDER_ASC], ], [ '$id' => '_key_user', - 'type' => Database::INDEX_UNIQUE, + 'type' => Database::INDEX_KEY, 'attributes' => ['userId'], 'lengths' => [Database::LENGTH_KEY], 'orders' => [Database::ORDER_ASC], From ca2dbb9fad38aa548ca0d2f37ffb10f9e0d12952 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Fri, 7 May 2021 00:36:27 +0300 Subject: [PATCH 20/60] Updated db lib --- composer.lock | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/composer.lock b/composer.lock index a18a54c132..9537c83569 100644 --- a/composer.lock +++ b/composer.lock @@ -1923,7 +1923,7 @@ "source": { "type": "git", "url": "https://github.com/utopia-php/database", - "reference": "a41bbe4674c2fea3ce84942e40f7c57b4903a918" + "reference": "11a149d2fd04b73c9fddffb93e96efc78d4a6039" }, "require": { "ext-mongodb": "*", @@ -1970,7 +1970,7 @@ "upf", "utopia" ], - "time": "2021-05-05T13:43:40+00:00" + "time": "2021-05-06T20:03:31+00:00" }, { "name": "utopia-php/domains", @@ -2679,7 +2679,7 @@ "source": { "type": "git", "url": "https://github.com/appwrite/sdk-generator", - "reference": "5bb8ceaf0ff9da85fcca840285d6896a7594f435" + "reference": "a8d90fd59db39ec04914692bd6ab06e013977b77" }, "require": { "ext-curl": "*", @@ -2687,7 +2687,7 @@ "ext-mbstring": "*", "matthiasmullie/minify": "^1.3", "php": ">=7.0.0", - "twig/twig": "^2.12" + "twig/twig": "^2.14" }, "require-dev": { "phpunit/phpunit": "^7.0" @@ -2709,7 +2709,7 @@ } ], "description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms", - "time": "2021-03-28T22:18:36+00:00" + "time": "2021-05-05T12:50:58+00:00" }, { "name": "composer/semver", @@ -2794,16 +2794,16 @@ }, { "name": "composer/xdebug-handler", - "version": "2.0.0", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/composer/xdebug-handler.git", - "reference": "31d57697eb1971712a08031cfaff5a846d10bdf5" + "reference": "964adcdd3a28bf9ed5d9ac6450064e0d71ed7496" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/31d57697eb1971712a08031cfaff5a846d10bdf5", - "reference": "31d57697eb1971712a08031cfaff5a846d10bdf5", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/964adcdd3a28bf9ed5d9ac6450064e0d71ed7496", + "reference": "964adcdd3a28bf9ed5d9ac6450064e0d71ed7496", "shasum": "" }, "require": { @@ -2838,7 +2838,7 @@ "support": { "irc": "irc://irc.freenode.org/composer", "issues": "https://github.com/composer/xdebug-handler/issues", - "source": "https://github.com/composer/xdebug-handler/tree/2.0.0" + "source": "https://github.com/composer/xdebug-handler/tree/2.0.1" }, "funding": [ { @@ -2854,7 +2854,7 @@ "type": "tidelift" } ], - "time": "2021-04-09T19:40:06+00:00" + "time": "2021-05-05T19:37:51+00:00" }, { "name": "dnoegel/php-xdg-base-dir", From e70de897d6a7a221644bca33e30b9db55b094c1e Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Fri, 7 May 2021 01:31:05 +0300 Subject: [PATCH 21/60] Work in progress account/teams/users --- app/controllers/api/account.php | 543 ++++++++++++------------------- app/controllers/api/projects.php | 4 +- app/controllers/api/teams.php | 349 ++++++++------------ app/controllers/api/users.php | 243 ++++++-------- app/init.php | 22 +- 5 files changed, 448 insertions(+), 713 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 2cf9770fc8..c7c8fe3ffc 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -1,30 +1,30 @@ inject('request') ->inject('response') ->inject('project') - ->inject('projectDB') + ->inject('dbForInternal') ->inject('audits') - ->action(function ($email, $password, $name, $request, $response, $project, $projectDB, $audits) { + ->action(function ($email, $password, $name, $request, $response, $project, $dbForInternal, $audits) { /** @var Utopia\Swoole\Request $request */ /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Document $project */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Database $dbForInternal */ /** @var Appwrite\Event\Event $audits */ if ('console' === $project->getId()) { @@ -79,40 +79,21 @@ App::post('/v1/account') $limit = $project->getAttribute('usersAuthLimit', 0); if ($limit !== 0) { - $projectDB->getCollection([ // Count users - 'filters' => [ - '$collection='.Database::SYSTEM_COLLECTION_USERS, - ], - ]); - - $sum = $projectDB->getSum(); + $sum = $dbForInternal->count('users'); // Count users TODO: add a 10k limit here. if($sum >= $limit) { throw new Exception('Project registration is restricted. Contact your administrator for more information.', 501); } } - $profile = $projectDB->getCollectionFirst([ // Get user by email address - 'limit' => 1, - 'filters' => [ - '$collection='.Database::SYSTEM_COLLECTION_USERS, - 'email='.$email, - ], - ]); - - if (!empty($profile)) { - throw new Exception('Account already exists', 409); - } - Authorization::disable(); try { - $user = $projectDB->createDocument([ - '$collection' => Database::SYSTEM_COLLECTION_USERS, - '$permissions' => [ - 'read' => ['*'], - 'write' => ['user:{self}'], - ], + $userId = $dbForInternal->getId(); + $user = $dbForInternal->createDocument('users', new Document([ + '$id' => $userId, + '$read' => ['*'], + '$write' => ['user:'.$userId], 'email' => $email, 'emailVerification' => false, 'status' => Auth::USER_STATUS_UNACTIVATED, @@ -121,7 +102,11 @@ App::post('/v1/account') 'registration' => \time(), 'reset' => false, 'name' => $name, - ], ['email' => $email]); + 'prefs' => [], + 'sessions' => [], + 'tokens' => [], + 'memberships' => [], + ])); } catch (Duplicate $th) { throw new Exception('Account already exists', 409); } @@ -142,10 +127,8 @@ App::post('/v1/account') ->setParam('resource', 'users/'.$user->getId()) ; - $response - ->setStatusCode(Response::STATUS_CODE_CREATED) - ->dynamic($user, Response::MODEL_USER) - ; + $response->setStatusCode(Response::STATUS_CODE_CREATED); + $response->dynamic2($user, Response::MODEL_USER); }); App::post('/v1/account/sessions') @@ -167,28 +150,23 @@ App::post('/v1/account/sessions') ->param('password', '', new Password(), 'User password. Must be between 6 to 32 chars.') ->inject('request') ->inject('response') - ->inject('projectDB') + ->inject('dbForInternal') ->inject('locale') ->inject('geodb') ->inject('audits') - ->action(function ($email, $password, $request, $response, $projectDB, $locale, $geodb, $audits) { + ->action(function ($email, $password, $request, $response, $dbForInternal, $locale, $geodb, $audits) { /** @var Utopia\Swoole\Request $request */ /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Database $dbForInternal */ /** @var Utopia\Locale\Locale $locale */ /** @var MaxMind\Db\Reader $geodb */ /** @var Appwrite\Event\Event $audits */ $protocol = $request->getProtocol(); - $profile = $projectDB->getCollectionFirst([ // Get user by email address - 'limit' => 1, - 'filters' => [ - '$collection='.Database::SYSTEM_COLLECTION_USERS, - 'email='.$email, - ], - ]); + + $profile = $dbForInternal->findFirst('users', [new Query('email', Query::TYPE_EQUAL, [$email])], 1); // Get user by email address - if (false == $profile || !Auth::passwordVerify($password, $profile->getAttribute('password'))) { + if (!$profile || !Auth::passwordVerify($password, $profile->getAttribute('password'))) { $audits //->setParam('userId', $profile->getId()) ->setParam('event', 'account.sesssions.failed') @@ -208,9 +186,7 @@ App::post('/v1/account/sessions') $secret = Auth::tokenGenerator(); $session = new Document(array_merge( [ - '$collection' => Database::SYSTEM_COLLECTION_SESSIONS, - '$permissions' => ['read' => ['user:'.$profile->getId()], 'write' => ['user:'.$profile->getId()]], - 'userId' => $profile->getId(), + '$id' => $dbForInternal->getId(), 'provider' => Auth::SESSION_PROVIDER_EMAIL, 'providerUid' => $email, 'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak @@ -223,15 +199,9 @@ App::post('/v1/account/sessions') Authorization::setRole('user:'.$profile->getId()); - $session = $projectDB->createDocument($session->getArrayCopy()); - - if (false === $session) { - throw new Exception('Failed saving session to DB', 500); - } - $profile->setAttribute('sessions', $session, Document::SET_TYPE_APPEND); - $profile = $projectDB->updateDocument($profile->getArrayCopy()); + $profile = $dbForInternal->updateDocument('users', $profile->getId(), $profile); if (false === $profile) { throw new Exception('Failed saving user to DB', 500); @@ -262,7 +232,7 @@ App::post('/v1/account/sessions') ->setAttribute('countryName', (isset($countries[strtoupper($session->getAttribute('countryCode'))])) ? $countries[strtoupper($session->getAttribute('countryCode'))] : $locale->getText('locale.country.unknown')) ; - $response->dynamic($session, Response::MODEL_SESSION); + $response->dynamic2($session, Response::MODEL_SESSION); }); App::get('/v1/account/sessions/oauth2/:provider') @@ -388,15 +358,15 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') ->inject('response') ->inject('project') ->inject('user') - ->inject('projectDB') + ->inject('dbForInternal') ->inject('geodb') ->inject('audits') - ->action(function ($provider, $code, $state, $request, $response, $project, $user, $projectDB, $geodb, $audits) use ($oauthDefaultSuccess) { + ->action(function ($provider, $code, $state, $request, $response, $project, $user, $dbForInternal, $geodb, $audits) use ($oauthDefaultSuccess) { /** @var Utopia\Swoole\Request $request */ /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Document $project */ - /** @var Appwrite\Database\Document $user */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Document $user */ + /** @var Utopia\Database\Database $dbForInternal */ /** @var MaxMind\Db\Reader $geodb */ /** @var Appwrite\Event\Event $audits */ @@ -460,13 +430,19 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') throw new Exception('Missing ID from OAuth2 provider', 400); } - $current = Auth::sessionVerify($user->getAttribute('sessions', []), Auth::$secret); + $sessions = $user->getAttribute('sessions', []); + $current = Auth::sessionVerify($sessions, Auth::$secret); - if ($current) { - $projectDB->deleteDocument($current); //throw new Exception('User already logged in', 401); + if($current) { // Delete current session of new one. + foreach ($sessions as $key => $session) { + if ($current === $session['$id']) { + unset($sessions[$key]); + $dbForInternal->updateDocument('users', $user->getId(), $user->setAttribute('sessions', $sessions)); + } + } } - $user = (empty($user->getId())) ? $projectDB->getCollectionFirst([ // Get user by provider id + $user = (empty($user->getId())) ? $dbForInternal->getCollectionFirst([ // Get user by provider id 'limit' => 1, 'filters' => [ '$collection='.Database::SYSTEM_COLLECTION_USERS, @@ -479,25 +455,13 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') $name = $oauth2->getUserName($accessToken); $email = $oauth2->getUserEmail($accessToken); - $user = $projectDB->getCollectionFirst([ // Get user by provider email address - 'limit' => 1, - 'filters' => [ - '$collection='.Database::SYSTEM_COLLECTION_USERS, - 'email='.$email, - ], - ]); + $user = $dbForInternal->findFirst('users', [new Query('email', Query::TYPE_EQUAL, [$email])], 1); // Get user by email address if (!$user || empty($user->getId())) { // Last option -> create the user, generate random password $limit = $project->getAttribute('usersAuthLimit', 0); if ($limit !== 0) { - $projectDB->getCollection([ // Count users - 'filters' => [ - '$collection='.Database::SYSTEM_COLLECTION_USERS, - ], - ]); - - $sum = $projectDB->getSum(); + $sum = $dbForInternal->count('users'); // Count users TODO: add a 10k limit here. if($sum >= $limit) { throw new Exception('Project registration is restricted. Contact your administrator for more information.', 501); @@ -507,9 +471,11 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') Authorization::disable(); try { - $user = $projectDB->createDocument([ - '$collection' => Database::SYSTEM_COLLECTION_USERS, - '$permissions' => ['read' => ['*'], 'write' => ['user:{self}']], + $userId = $dbForInternal->getId(); + $user = $dbForInternal->createDocument('users', new Document([ + '$id' => $userId, + '$read' => ['*'], + '$write' => ['user:'.$userId], 'email' => $email, 'emailVerification' => true, 'status' => Auth::USER_STATUS_ACTIVATED, // Email should already be authenticated by OAuth2 provider @@ -518,16 +484,12 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') 'registration' => \time(), 'reset' => false, 'name' => $name, - ], ['email' => $email]); + ])); } catch (Duplicate $th) { throw new Exception('Account already exists', 409); } Authorization::reset(); - - if (false === $user) { - throw new Exception('Failed saving user to DB', 500); - } } } @@ -542,9 +504,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') $secret = Auth::tokenGenerator(); $expiry = \time() + Auth::TOKEN_EXPIRATION_LOGIN_LONG; $session = new Document(array_merge([ - '$collection' => Database::SYSTEM_COLLECTION_SESSIONS, - '$permissions' => ['read' => ['user:'.$user['$id']], 'write' => ['user:'.$user['$id']]], - 'userId' => $user->getId(), + '$id' => $dbForInternal->getId(), 'provider' => $provider, 'providerUid' => $oauth2ID, 'providerToken' => $accessToken, @@ -571,7 +531,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') Authorization::setRole('user:'.$user->getId()); - $user = $projectDB->updateDocument($user->getArrayCopy()); + $user = $dbForInternal->updateDocument('users', $user->getId(), $user); if (false === $user) { throw new Exception('Failed saving user to DB', 500); @@ -631,16 +591,16 @@ App::post('/v1/account/sessions/anonymous') ->inject('locale') ->inject('user') ->inject('project') - ->inject('projectDB') + ->inject('dbForInternal') ->inject('geodb') ->inject('audits') - ->action(function ($request, $response, $locale, $user, $project, $projectDB, $geodb, $audits) { + ->action(function ($request, $response, $locale, $user, $project, $dbForInternal, $geodb, $audits) { /** @var Utopia\Swoole\Request $request */ /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Locale\Locale $locale */ - /** @var Appwrite\Database\Document $user */ + /** @var Utopia\Database\Document $user */ /** @var Appwrite\Database\Document $project */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Database $dbForInternal */ /** @var MaxMind\Db\Reader $geodb */ /** @var Appwrite\Event\Event $audits */ @@ -657,13 +617,7 @@ App::post('/v1/account/sessions/anonymous') $limit = $project->getAttribute('usersAuthLimit', 0); if ($limit !== 0) { - $projectDB->getCollection([ // Count users - 'filters' => [ - '$collection='.Database::SYSTEM_COLLECTION_USERS, - ], - ]); - - $sum = $projectDB->getSum(); + $sum = $dbForInternal->count('users'); // Count users TODO: add a 10k limit here. if($sum >= $limit) { throw new Exception('Project registration is restricted. Contact your administrator for more information.', 501); @@ -672,12 +626,11 @@ App::post('/v1/account/sessions/anonymous') Authorization::disable(); try { - $user = $projectDB->createDocument([ - '$collection' => Database::SYSTEM_COLLECTION_USERS, - '$permissions' => [ - 'read' => ['*'], - 'write' => ['user:{self}'] - ], + $userId = $dbForInternal->getId(); + $user = $dbForInternal->createDocument('users', new Document([ + '$id' => $userId, + '$read' => ['*'], + '$write' => ['user:'.$userId], 'email' => null, 'emailVerification' => false, 'status' => Auth::USER_STATUS_UNACTIVATED, @@ -686,7 +639,7 @@ App::post('/v1/account/sessions/anonymous') 'registration' => \time(), 'reset' => false, 'name' => null - ]); + ])); } catch (Exception $th) { throw new Exception('Failed saving user to DB', 500); } @@ -704,9 +657,7 @@ App::post('/v1/account/sessions/anonymous') $expiry = \time() + Auth::TOKEN_EXPIRATION_LOGIN_LONG; $session = new Document(array_merge( [ - '$collection' => Database::SYSTEM_COLLECTION_SESSIONS, - '$permissions' => ['read' => ['user:' . $user['$id']], 'write' => ['user:' . $user['$id']]], - 'userId' => $user->getId(), + '$id' => $dbForInternal->getId(), 'provider' => Auth::SESSION_PROVIDER_ANONYMOUS, 'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak 'expire' => $expiry, @@ -723,7 +674,7 @@ App::post('/v1/account/sessions/anonymous') Authorization::setRole('user:'.$user->getId()); - $user = $projectDB->updateDocument($user->getArrayCopy()); + $user = $dbForInternal->updateDocument('users', $user->getId(), $user); if (false === $user) { throw new Exception('Failed saving user to DB', 500); @@ -752,7 +703,7 @@ App::post('/v1/account/sessions/anonymous') ->setAttribute('countryName', (isset($countries[$session->getAttribute('countryCode')])) ? $countries[$session->getAttribute('countryCode')] : $locale->getText('locale.country.unknown')) ; - $response->dynamic($session, Response::MODEL_SESSION); + $response->dynamic2($session, Response::MODEL_SESSION); }); App::post('/v1/account/jwt') @@ -770,7 +721,7 @@ App::post('/v1/account/jwt') ->inject('user') ->action(function ($response, $user) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Document $user */ + /** @var Utopia\Database\Document $user */ $sessions = $user->getAttribute('sessions', []); $current = new Document(); @@ -789,16 +740,15 @@ App::post('/v1/account/jwt') $jwt = new JWT(App::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 900, 10); // Instantiate with key, algo, maxAge and leeway. - $response - ->setStatusCode(Response::STATUS_CODE_CREATED) - ->dynamic(new Document(['jwt' => $jwt->encode([ - // 'uid' => 1, - // 'aud' => 'http://site.com', - // 'scopes' => ['user'], - // 'iss' => 'http://api.mysite.com', - 'userId' => $user->getId(), - 'sessionId' => $current->getId(), - ])]), Response::MODEL_JWT); + $response->setStatusCode(Response::STATUS_CODE_CREATED); + $response->dynamic2(new Document(['jwt' => $jwt->encode([ + // 'uid' => 1, + // 'aud' => 'http://site.com', + // 'scopes' => ['user'], + // 'iss' => 'http://api.mysite.com', + 'userId' => $user->getId(), + 'sessionId' => $current->getId(), + ])]), Response::MODEL_JWT); }); App::get('/v1/account') @@ -816,9 +766,9 @@ App::get('/v1/account') ->inject('user') ->action(function ($response, $user) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Document $user */ + /** @var Utopia\Database\Document $user */ - $response->dynamic($user, Response::MODEL_USER); + $response->dynamic2($user, Response::MODEL_USER); }); App::get('/v1/account/prefs') @@ -836,11 +786,11 @@ App::get('/v1/account/prefs') ->inject('user') ->action(function ($response, $user) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Document $user */ + /** @var Utopia\Database\Document $user */ $prefs = $user->getAttribute('prefs', new \stdClass()); - $response->dynamic(new Document($prefs), Response::MODEL_PREFERENCES); + $response->dynamic2(new Document($prefs), Response::MODEL_PREFERENCES); }); App::get('/v1/account/sessions') @@ -859,7 +809,7 @@ App::get('/v1/account/sessions') ->inject('locale') ->action(function ($response, $user, $locale) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Document $user */ + /** @var Utopia\Database\Document $user */ /** @var Utopia\Locale\Locale $locale */ $sessions = $user->getAttribute('sessions', []); @@ -877,7 +827,7 @@ App::get('/v1/account/sessions') $sessions[$key] = $session; } - $response->dynamic(new Document([ + $response->dynamic2(new Document([ 'sum' => count($sessions), 'sessions' => $sessions ]), Response::MODEL_SESSION_LIST); @@ -903,7 +853,7 @@ App::get('/v1/account/logs') ->action(function ($response, $register, $project, $user, $locale, $geodb) { /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Document $project */ - /** @var Appwrite\Database\Document $user */ + /** @var Utopia\Database\Document $user */ /** @var Utopia\Locale\Locale $locale */ /** @var MaxMind\Db\Reader $geodb */ @@ -956,7 +906,7 @@ App::get('/v1/account/logs') } - $response->dynamic(new Document(['logs' => $output]), Response::MODEL_LOG_LIST); + $response->dynamic2(new Document(['logs' => $output]), Response::MODEL_LOG_LIST); }); App::patch('/v1/account/name') @@ -974,21 +924,15 @@ App::patch('/v1/account/name') ->param('name', '', new Text(128), 'User name. Max length: 128 chars.') ->inject('response') ->inject('user') - ->inject('projectDB') + ->inject('dbForInternal') ->inject('audits') - ->action(function ($name, $response, $user, $projectDB, $audits) { + ->action(function ($name, $response, $user, $dbForInternal, $audits) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Document $user */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Document $user */ + /** @var Utopia\Database\Database $dbForInternal */ /** @var Appwrite\Event\Event $audits */ - $user = $projectDB->updateDocument(\array_merge($user->getArrayCopy(), [ - 'name' => $name, - ])); - - if (false === $user) { - throw new Exception('Failed saving user to DB', 500); - } + $user = $dbForInternal->updateDocument('users', $user->getId(), $user->setAttribute('name', $name)); $audits ->setParam('userId', $user->getId()) @@ -996,7 +940,7 @@ App::patch('/v1/account/name') ->setParam('resource', 'users/'.$user->getId()) ; - $response->dynamic($user, Response::MODEL_USER); + $response->dynamic2($user, Response::MODEL_USER); }); App::patch('/v1/account/password') @@ -1015,25 +959,19 @@ App::patch('/v1/account/password') ->param('oldPassword', '', new Password(), 'Old user password. Must be between 6 to 32 chars.') ->inject('response') ->inject('user') - ->inject('projectDB') + ->inject('dbForInternal') ->inject('audits') - ->action(function ($password, $oldPassword, $response, $user, $projectDB, $audits) { + ->action(function ($password, $oldPassword, $response, $user, $dbForInternal, $audits) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Document $user */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Document $user */ + /** @var Utopia\Database\Database $dbForInternal */ /** @var Appwrite\Event\Event $audits */ if (!Auth::passwordVerify($oldPassword, $user->getAttribute('password'))) { // Double check user password throw new Exception('Invalid credentials', 401); } - $user = $projectDB->updateDocument(\array_merge($user->getArrayCopy(), [ - 'password' => Auth::passwordHash($password), - ])); - - if (false === $user) { - throw new Exception('Failed saving user to DB', 500); - } + $user = $dbForInternal->updateDocument('users', $user->getId(), $user->setAttribute('password', Auth::passwordHash($password))); $audits ->setParam('userId', $user->getId()) @@ -1041,7 +979,7 @@ App::patch('/v1/account/password') ->setParam('resource', 'users/'.$user->getId()) ; - $response->dynamic($user, Response::MODEL_USER); + $response->dynamic2($user, Response::MODEL_USER); }); App::patch('/v1/account/email') @@ -1060,12 +998,12 @@ App::patch('/v1/account/email') ->param('password', '', new Password(), 'User password. Must be between 6 to 32 chars.') ->inject('response') ->inject('user') - ->inject('projectDB') + ->inject('dbForInternal') ->inject('audits') - ->action(function ($email, $password, $response, $user, $projectDB, $audits) { + ->action(function ($email, $password, $response, $user, $dbForInternal, $audits) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Document $user */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Document $user */ + /** @var Utopia\Database\Database $dbForInternal */ /** @var Appwrite\Event\Event $audits */ $isAnonymousUser = is_null($user->getAttribute('email')) && is_null($user->getAttribute('password')); // Check if request is from an anonymous account for converting @@ -1077,28 +1015,17 @@ App::patch('/v1/account/email') throw new Exception('Invalid credentials', 401); } - $profile = $projectDB->getCollectionFirst([ // Get user by email address - 'limit' => 1, - 'filters' => [ - '$collection='.Database::SYSTEM_COLLECTION_USERS, - 'email='.$email, - ], - ]); + $profile = $dbForInternal->findFirst('users', [new Query('email', Query::TYPE_EQUAL, [$email])], 1); // Get user by email address - if (!empty($profile)) { + if ($profile) { throw new Exception('User already registered', 400); } - // TODO after this user needs to confirm mail again - - $user = $projectDB->updateDocument(\array_merge( - $user->getArrayCopy(), - ($isAnonymousUser ? [ 'password' => Auth::passwordHash($password) ] : []), - [ - 'email' => $email, - 'emailVerification' => false, - ] - )); + $user = $dbForInternal->updateDocument('users', $user->getId(), $user + ->setAttribute('password', $isAnonymousUser ? Auth::passwordHash($password) : $user->getAttribute('password', '')) + ->setAttribute('email', $email) + ->setAttribute('emailVerification', false) // After this user needs to confirm mail again + ); if (false === $user) { throw new Exception('Failed saving user to DB', 500); @@ -1110,7 +1037,7 @@ App::patch('/v1/account/email') ->setParam('resource', 'users/'.$user->getId()) ; - $response->dynamic($user, Response::MODEL_USER); + $response->dynamic2($user, Response::MODEL_USER); }); App::patch('/v1/account/prefs') @@ -1128,28 +1055,22 @@ App::patch('/v1/account/prefs') ->param('prefs', [], new Assoc(), 'Prefs key-value JSON object.') ->inject('response') ->inject('user') - ->inject('projectDB') + ->inject('dbForInternal') ->inject('audits') - ->action(function ($prefs, $response, $user, $projectDB, $audits) { + ->action(function ($prefs, $response, $user, $dbForInternal, $audits) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Document $user */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Document $user */ + /** @var Utopia\Database\Database $dbForInternal */ /** @var Appwrite\Event\Event $audits */ - - $user = $projectDB->updateDocument(\array_merge($user->getArrayCopy(), [ - 'prefs' => $prefs, - ])); - if (false === $user) { - throw new Exception('Failed saving user to DB', 500); - } + $user = $dbForInternal->updateDocument('users', $user->getId(), $user->setAttribute('prefs', $prefs)); $audits ->setParam('event', 'account.update.prefs') ->setParam('resource', 'users/'.$user->getId()) ; - $response->dynamic($user, Response::MODEL_USER); + $response->dynamic2($user, Response::MODEL_USER); }); App::delete('/v1/account') @@ -1166,21 +1087,20 @@ App::delete('/v1/account') ->inject('request') ->inject('response') ->inject('user') - ->inject('projectDB') + ->inject('dbForInternal') ->inject('audits') ->inject('events') - ->action(function ($request, $response, $user, $projectDB, $audits, $events) { + ->action(function ($request, $response, $user, $dbForInternal, $audits, $events) { /** @var Utopia\Swoole\Request $request */ /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Document $user */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Document $user */ + /** @var Utopia\Database\Database $dbForInternal */ /** @var Appwrite\Event\Event $audits */ /** @var Appwrite\Event\Event $events */ $protocol = $request->getProtocol(); - $user = $projectDB->updateDocument(\array_merge($user->getArrayCopy(), [ - 'status' => Auth::USER_STATUS_BLOCKED, - ])); + $user = $dbForInternal->updateDocument('users', $user->getId(), $user->setAttribute('status', Auth::USER_STATUS_BLOCKED)); + if (false === $user) { throw new Exception('Failed saving user to DB', 500); @@ -1202,7 +1122,7 @@ App::delete('/v1/account') ; $events - ->setParam('eventData', $response->output($user, Response::MODEL_USER)) + ->setParam('eventData', $response->output2($user, Response::MODEL_USER)) ; if (!Config::getParam('domainVerification')) { @@ -1234,14 +1154,14 @@ App::delete('/v1/account/sessions/:sessionId') ->inject('request') ->inject('response') ->inject('user') - ->inject('projectDB') + ->inject('dbForInternal') ->inject('audits') ->inject('events') - ->action(function ($sessionId, $request, $response, $user, $projectDB, $audits, $events) { + ->action(function ($sessionId, $request, $response, $user, $dbForInternal, $audits, $events) { /** @var Utopia\Swoole\Request $request */ /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Document $user */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Document $user */ + /** @var Utopia\Database\Database $dbForInternal */ /** @var Appwrite\Event\Event $audits */ /** @var Appwrite\Event\Event $events */ @@ -1252,13 +1172,11 @@ App::delete('/v1/account/sessions/:sessionId') $sessions = $user->getAttribute('sessions', []); - foreach ($sessions as $session) { + foreach ($sessions as $key => $session) { /** @var Document $session */ - if (($sessionId == $session->getId())) { - if (!$projectDB->deleteDocument($session->getId())) { - throw new Exception('Failed to remove token from DB', 500); - } + if ($sessionId == $session->getId()) { + unset($sessions[$key]); $audits ->setParam('userId', $user->getId()) @@ -1283,8 +1201,10 @@ App::delete('/v1/account/sessions/:sessionId') ; } + $dbForInternal->updateDocument('users', $user->getId(), $user->setAttribute('sessions', $sessions)); + $events - ->setParam('eventData', $response->output($session, Response::MODEL_SESSION)) + ->setParam('eventData', $response->output2($session, Response::MODEL_SESSION)) ; return $response->noContent(); @@ -1309,14 +1229,14 @@ App::delete('/v1/account/sessions') ->inject('request') ->inject('response') ->inject('user') - ->inject('projectDB') + ->inject('dbForInternal') ->inject('audits') ->inject('events') - ->action(function ($request, $response, $user, $projectDB, $audits, $events) { + ->action(function ($request, $response, $user, $dbForInternal, $audits, $events) { /** @var Utopia\Swoole\Request $request */ /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Document $user */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Document $user */ + /** @var Utopia\Database\Database $dbForInternal */ /** @var Appwrite\Event\Event $audits */ /** @var Appwrite\Event\Event $events */ @@ -1326,10 +1246,6 @@ App::delete('/v1/account/sessions') foreach ($sessions as $session) { /** @var Document $session */ - if (!$projectDB->deleteDocument($session->getId())) { - throw new Exception('Failed to remove token from DB', 500); - } - $audits ->setParam('userId', $user->getId()) ->setParam('event', 'account.sessions.delete') @@ -1352,9 +1268,11 @@ App::delete('/v1/account/sessions') ; } } + + $dbForInternal->updateDocument('users', $user->getId(), $user->setAttribute('sessions', [])); $events - ->setParam('eventData', $response->output(new Document([ + ->setParam('eventData', $response->output2(new Document([ 'sum' => count($sessions), 'sessions' => $sessions ]), Response::MODEL_SESSION_LIST)) @@ -1381,16 +1299,16 @@ App::post('/v1/account/recovery') ->param('url', '', function ($clients) { return new Host($clients); }, 'URL to redirect the user back to your app from the recovery email. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', false, ['clients']) ->inject('request') ->inject('response') - ->inject('projectDB') + ->inject('dbForInternal') ->inject('project') ->inject('locale') ->inject('mails') ->inject('audits') ->inject('events') - ->action(function ($email, $url, $request, $response, $projectDB, $project, $locale, $mails, $audits, $events) { + ->action(function ($email, $url, $request, $response, $dbForInternal, $project, $locale, $mails, $audits, $events) { /** @var Utopia\Swoole\Request $request */ /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Database $dbForInternal */ /** @var Appwrite\Database\Document $project */ /** @var Utopia\Locale\Locale $locale */ /** @var Appwrite\Event\Event $mails */ @@ -1400,15 +1318,9 @@ App::post('/v1/account/recovery') $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::$roles); $isAppUser = Auth::isAppUser(Authorization::$roles); - $profile = $projectDB->getCollectionFirst([ // Get user by email address - 'limit' => 1, - 'filters' => [ - '$collection='.Database::SYSTEM_COLLECTION_USERS, - 'email='.$email, - ], - ]); + $profile = $dbForInternal->findFirst('users', [new Query('email', Query::TYPE_EQUAL, [$email])], 1); // Get user by email address - if (empty($profile)) { + if (!$profile) { throw new Exception('User not found', 404); // TODO maybe hide this } @@ -1418,9 +1330,7 @@ App::post('/v1/account/recovery') $secret = Auth::tokenGenerator(); $recovery = new Document([ - '$collection' => Database::SYSTEM_COLLECTION_TOKENS, - '$permissions' => ['read' => ['user:'.$profile->getId()], 'write' => ['user:'.$profile->getId()]], - 'userId' => $profile->getId(), + '$id' => $dbForInternal->getId(), 'type' => Auth::TOKEN_TYPE_RECOVERY, 'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak 'expire' => \time() + Auth::TOKEN_EXPIRATION_RECOVERY, @@ -1430,19 +1340,9 @@ App::post('/v1/account/recovery') Authorization::setRole('user:'.$profile->getId()); - $recovery = $projectDB->createDocument($recovery->getArrayCopy()); - - if (false === $recovery) { - throw new Exception('Failed saving recovery to DB', 500); - } - $profile->setAttribute('tokens', $recovery, Document::SET_TYPE_APPEND); - $profile = $projectDB->updateDocument($profile->getArrayCopy()); - - if (false === $profile) { - throw new Exception('Failed to save user to DB', 500); - } + $profile = $dbForInternal->updateDocument('users', $profile->getId(), $profile); $url = Template::parseURL($url); $url['query'] = Template::mergeQuery(((isset($url['query'])) ? $url['query'] : ''), ['userId' => $profile->getId(), 'secret' => $secret]); @@ -1480,7 +1380,7 @@ App::post('/v1/account/recovery') $events ->setParam('eventData', - $response->output($recovery->setAttribute('secret', $secret), + $response->output2($recovery->setAttribute('secret', $secret), Response::MODEL_TOKEN )) ; @@ -1495,10 +1395,8 @@ App::post('/v1/account/recovery') ->setParam('resource', 'users/'.$profile->getId()) ; - $response - ->setStatusCode(Response::STATUS_CODE_CREATED) - ->dynamic($recovery, Response::MODEL_TOKEN) - ; + $response->setStatusCode(Response::STATUS_CODE_CREATED); + $response->dynamic2($recovery, Response::MODEL_TOKEN); }); App::put('/v1/account/recovery') @@ -1520,30 +1418,25 @@ App::put('/v1/account/recovery') ->param('password', '', new Password(), 'New password. Must be between 6 to 32 chars.') ->param('passwordAgain', '', new Password(), 'New password again. Must be between 6 to 32 chars.') ->inject('response') - ->inject('projectDB') + ->inject('dbForInternal') ->inject('audits') - ->action(function ($userId, $secret, $password, $passwordAgain, $response, $projectDB, $audits) { + ->action(function ($userId, $secret, $password, $passwordAgain, $response, $dbForInternal, $audits) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Database $dbForInternal */ /** @var Appwrite\Event\Event $audits */ if ($password !== $passwordAgain) { throw new Exception('Passwords must match', 400); } - $profile = $projectDB->getCollectionFirst([ // Get user by email address - 'limit' => 1, - 'filters' => [ - '$collection='.Database::SYSTEM_COLLECTION_USERS, - '$id='.$userId, - ], - ]); + $profile = $dbForInternal->getDocument('users', $userId); - if (empty($profile)) { + if ($profile->isEmpty()) { throw new Exception('User not found', 404); // TODO maybe hide this } - $recovery = Auth::tokenVerify($profile->getAttribute('tokens', []), Auth::TOKEN_TYPE_RECOVERY, $secret); + $tokens = $profile->getAttribute('tokens', []); + $recovery = Auth::tokenVerify($tokens, Auth::TOKEN_TYPE_RECOVERY, $secret); if (!$recovery) { throw new Exception('Invalid recovery token', 401); @@ -1551,33 +1444,33 @@ App::put('/v1/account/recovery') Authorization::setRole('user:'.$profile->getId()); - $profile = $projectDB->updateDocument(\array_merge($profile->getArrayCopy(), [ - 'password' => Auth::passwordHash($password), - 'passwordUpdate' => \time(), - 'emailVerification' => true, - ])); - - if (false === $profile) { - throw new Exception('Failed saving user to DB', 500); - } + $profile = $dbForInternal->updateDocument('users', $profile->getId(), $profile + ->setAttribute('password', Auth::passwordHash($password)) + ->setAttribute('passwordUpdate', \time()) + ->setAttribute('emailVerification', true) + ); /** * We act like we're updating and validating * the recovery token but actually we don't need it anymore. */ - if (!$projectDB->deleteDocument($recovery)) { - throw new Exception('Failed to remove recovery from DB', 500); + + foreach ($tokens as $key => $token) { + if($recovery === $token->getId()) { + $recovery = $token; + unset($tokens[$key]); + } } + $dbForInternal->updateDocument('users', $profile->getId(), $profile->setAttribute('tokens', $tokens)); + $audits ->setParam('userId', $profile->getId()) ->setParam('event', 'account.recovery.update') ->setParam('resource', 'users/'.$profile->getId()) ; - $recovery = $profile->search('$id', $recovery, $profile->getAttribute('tokens', [])); - - $response->dynamic($recovery, Response::MODEL_TOKEN); + $response->dynamic2($recovery, Response::MODEL_TOKEN); }); App::post('/v1/account/verification') @@ -1599,17 +1492,17 @@ App::post('/v1/account/verification') ->inject('response') ->inject('project') ->inject('user') - ->inject('projectDB') + ->inject('dbForInternal') ->inject('locale') ->inject('audits') ->inject('events') ->inject('mails') - ->action(function ($url, $request, $response, $project, $user, $projectDB, $locale, $audits, $events, $mails) { + ->action(function ($url, $request, $response, $project, $user, $dbForInternal, $locale, $audits, $events, $mails) { /** @var Utopia\Swoole\Request $request */ /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Document $project */ - /** @var Appwrite\Database\Document $user */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Document $user */ + /** @var Utopia\Database\Database $dbForInternal */ /** @var Utopia\Locale\Locale $locale */ /** @var Appwrite\Event\Event $audits */ /** @var Appwrite\Event\Event $events */ @@ -1621,9 +1514,7 @@ App::post('/v1/account/verification') $verificationSecret = Auth::tokenGenerator(); $verification = new Document([ - '$collection' => Database::SYSTEM_COLLECTION_TOKENS, - '$permissions' => ['read' => ['user:'.$user->getId()], 'write' => ['user:'.$user->getId()]], - 'userId' => $user->getId(), + '$id' => $dbForInternal->getId(), 'type' => Auth::TOKEN_TYPE_VERIFICATION, 'secret' => Auth::hash($verificationSecret), // One way hash encryption to protect DB leak 'expire' => \time() + Auth::TOKEN_EXPIRATION_CONFIRM, @@ -1633,19 +1524,9 @@ App::post('/v1/account/verification') Authorization::setRole('user:'.$user->getId()); - $verification = $projectDB->createDocument($verification->getArrayCopy()); - - if (false === $verification) { - throw new Exception('Failed saving verification to DB', 500); - } - $user->setAttribute('tokens', $verification, Document::SET_TYPE_APPEND); - $user = $projectDB->updateDocument($user->getArrayCopy()); - - if (false === $user) { - throw new Exception('Failed to save user to DB', 500); - } + $user = $dbForInternal->updateDocument('users', $user->getId(), $user); $url = Template::parseURL($url); $url['query'] = Template::mergeQuery(((isset($url['query'])) ? $url['query'] : ''), ['userId' => $user->getId(), 'secret' => $verificationSecret]); @@ -1683,7 +1564,7 @@ App::post('/v1/account/verification') $events ->setParam('eventData', - $response->output($verification->setAttribute('secret', $verificationSecret), + $response->output2($verification->setAttribute('secret', $verificationSecret), Response::MODEL_TOKEN )) ; @@ -1698,10 +1579,8 @@ App::post('/v1/account/verification') ->setParam('resource', 'users/'.$user->getId()) ; - $response - ->setStatusCode(Response::STATUS_CODE_CREATED) - ->dynamic($verification, Response::MODEL_TOKEN) - ; + $response->setStatusCode(Response::STATUS_CODE_CREATED); + $response->dynamic2($verification, Response::MODEL_TOKEN); }); App::put('/v1/account/verification') @@ -1722,27 +1601,22 @@ App::put('/v1/account/verification') ->param('secret', '', new Text(256), 'Valid verification token.') ->inject('response') ->inject('user') - ->inject('projectDB') + ->inject('dbForInternal') ->inject('audits') - ->action(function ($userId, $secret, $response, $user, $projectDB, $audits) { + ->action(function ($userId, $secret, $response, $user, $dbForInternal, $audits) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Document $user */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Document $user */ + /** @var Utopia\Database\Database $dbForInternal */ /** @var Appwrite\Event\Event $audits */ - $profile = $projectDB->getCollectionFirst([ // Get user by email address - 'limit' => 1, - 'filters' => [ - '$collection='.Database::SYSTEM_COLLECTION_USERS, - '$id='.$userId, - ], - ]); + $profile = $dbForInternal->getDocument('users', $userId); - if (empty($profile)) { + if ($profile->isEmpty()) { throw new Exception('User not found', 404); // TODO maybe hide this } - $verification = Auth::tokenVerify($profile->getAttribute('tokens', []), Auth::TOKEN_TYPE_VERIFICATION, $secret); + $tokens = $profile->getAttribute('tokens', []); + $verification = Auth::tokenVerify($tokens, Auth::TOKEN_TYPE_VERIFICATION, $secret); if (!$verification) { throw new Exception('Invalid verification token', 401); @@ -1750,9 +1624,7 @@ App::put('/v1/account/verification') Authorization::setRole('user:'.$profile->getId()); - $profile = $projectDB->updateDocument(\array_merge($profile->getArrayCopy(), [ - 'emailVerification' => true, - ])); + $profile = $dbForInternal->updateDocument('users', $profile->getId(), $profile->setAttribute('emailVerification', true)); if (false === $profile) { throw new Exception('Failed saving user to DB', 500); @@ -1762,17 +1634,20 @@ App::put('/v1/account/verification') * We act like we're updating and validating * the verification token but actually we don't need it anymore. */ - if (!$projectDB->deleteDocument($verification)) { - throw new Exception('Failed to remove verification from DB', 500); + foreach ($tokens as $key => $token) { + if($token->getId() === $verification) { + $verification = $token; + unset($tokens[$key]); + } } + $dbForInternal->updateDocument('users', $profile->getId(), $profile->setAttribute('tokens', $tokens)); + $audits ->setParam('userId', $profile->getId()) ->setParam('event', 'account.verification.update') ->setParam('resource', 'users/'.$user->getId()) ; - $verification = $profile->search('$id', $verification, $profile->getAttribute('tokens', [])); - - $response->dynamic($verification, Response::MODEL_TOKEN); + $response->dynamic2($verification, Response::MODEL_TOKEN); }); \ No newline at end of file diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index ff845bd674..0b5c1cb200 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -54,9 +54,9 @@ App::post('/v1/projects') /** @var Utopia\Database\Database $dbForInternal */ /** @var Utopia\Database\Database $dbForExternal */ - $team = $projectDB->getDocument($teamId); + $team = $dbForInternal->getDocument('teams', $teamId); - if (empty($team->getId()) || Database::SYSTEM_COLLECTION_TEAMS != $team->getCollection()) { + if ($team->isEmpty()) { throw new Exception('Team not found', 404); } diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index 910427da14..bc6ae280bd 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -1,24 +1,24 @@ desc('Create Team') @@ -36,41 +36,33 @@ App::post('/v1/teams') ->param('roles', ['owner'], new ArrayList(new Key()), 'Array of strings. Use this param to set the roles in the team for the user who created it. The default role is **owner**. A role can be any string. Learn more about [roles and permissions](/docs/permissions). Max length for each role is 32 chars.', true) ->inject('response') ->inject('user') - ->inject('projectDB') - ->action(function ($name, $roles, $response, $user, $projectDB) { + ->inject('dbForInternal') + ->action(function ($name, $roles, $response, $user, $dbForInternal) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Document $user */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Document $user */ + /** @var Utopia\Database\Database $dbForInternal */ Authorization::disable(); $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::$roles); $isAppUser = Auth::isAppUser(Authorization::$roles); - $team = $projectDB->createDocument([ - '$collection' => Database::SYSTEM_COLLECTION_TEAMS, - '$permissions' => [ - 'read' => ['team:{self}'], - 'write' => ['team:{self}/owner'], - ], + $teamId = $dbForInternal->getId(); + $team = $dbForInternal->createDocument('teams', new Document([ + '$id' => $teamId , + '$read' => ['team:'.$teamId ], + '$write' => ['team:'.$teamId .'/owner'], 'name' => $name, 'sum' => ($isPrivilegedUser || $isAppUser) ? 0 : 1, 'dateCreated' => \time(), - ]); + ])); Authorization::reset(); - if (false === $team) { - throw new Exception('Failed saving team to DB', 500); - } - if (!$isPrivilegedUser && !$isAppUser) { // Don't add user on server mode $membership = new Document([ - '$collection' => Database::SYSTEM_COLLECTION_MEMBERSHIPS, - '$permissions' => [ - 'read' => ['user:'.$user->getId(), 'team:'.$team->getId()], - 'write' => ['user:'.$user->getId(), 'team:'.$team->getId().'/owner'], - ], + '$read' => ['user:'.$user->getId(), 'team:'.$team->getId()], + '$write' => ['user:'.$user->getId(), 'team:'.$team->getId().'/owner'], 'userId' => $user->getId(), 'teamId' => $team->getId(), 'roles' => $roles, @@ -80,20 +72,16 @@ App::post('/v1/teams') 'secret' => '', ]); + $membership = $dbForInternal->createDocument('memberships', $membership); + // Attach user to team $user->setAttribute('memberships', $membership, Document::SET_TYPE_APPEND); - $user = $projectDB->updateDocument($user->getArrayCopy()); - - if (false === $user) { - throw new Exception('Failed saving user to DB', 500); - } + $user = $dbForInternal->updateDocument('users', $user->getId(), $user); } - $response - ->setStatusCode(Response::STATUS_CODE_CREATED) - ->dynamic($team, Response::MODEL_TEAM) - ; + $response->setStatusCode(Response::STATUS_CODE_CREATED); + $response->dynamic2($team, Response::MODEL_TEAM); }); App::get('/v1/teams') @@ -112,23 +100,16 @@ App::get('/v1/teams') ->param('offset', 0, new Range(0, 2000), 'Results offset. The default value is 0. Use this param to manage pagination.', true) ->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true) ->inject('response') - ->inject('projectDB') - ->action(function ($search, $limit, $offset, $orderType, $response, $projectDB) { + ->inject('dbForInternal') + ->action(function ($search, $limit, $offset, $orderType, $response, $dbForInternal) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Database $dbForInternal */ - $results = $projectDB->getCollection([ - 'limit' => $limit, - 'offset' => $offset, - 'orderType' => $orderType, - 'search' => $search, - 'filters' => [ - '$collection='.Database::SYSTEM_COLLECTION_TEAMS, - ], - ]); + $results = $dbForInternal->find('teams', [], $limit, $offset); + $sum = $dbForInternal->count('teams', [], 5000); - $response->dynamic(new Document([ - 'sum' => $projectDB->getSum(), + $response->dynamic2(new Document([ + 'sum' => $sum, 'teams' => $results ]), Response::MODEL_TEAM_LIST); }); @@ -146,18 +127,18 @@ App::get('/v1/teams/:teamId') ->label('sdk.response.model', Response::MODEL_TEAM) ->param('teamId', '', new UID(), 'Team unique ID.') ->inject('response') - ->inject('projectDB') - ->action(function ($teamId, $response, $projectDB) { + ->inject('dbForInternal') + ->action(function ($teamId, $response, $dbForInternal) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Database $dbForInternal */ - $team = $projectDB->getDocument($teamId); + $team = $dbForInternal->getDocument('teams', $teamId); - if (empty($team->getId()) || Database::SYSTEM_COLLECTION_TEAMS != $team->getCollection()) { + if (empty($team->getId())) { throw new Exception('Team not found', 404); } - $response->dynamic($team, Response::MODEL_TEAM); + $response->dynamic2($team, Response::MODEL_TEAM); }); App::put('/v1/teams/:teamId') @@ -175,26 +156,20 @@ App::put('/v1/teams/:teamId') ->param('teamId', '', new UID(), 'Team unique ID.') ->param('name', null, new Text(128), 'Team name. Max length: 128 chars.') ->inject('response') - ->inject('projectDB') - ->action(function ($teamId, $name, $response, $projectDB) { + ->inject('dbForInternal') + ->action(function ($teamId, $name, $response, $dbForInternal) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Database $dbForInternal */ - $team = $projectDB->getDocument($teamId); + $team = $dbForInternal->getDocument('teams', $teamId); - if (empty($team->getId()) || Database::SYSTEM_COLLECTION_TEAMS != $team->getCollection()) { + if (empty($team->getId())) { throw new Exception('Team not found', 404); } - $team = $projectDB->updateDocument(\array_merge($team->getArrayCopy(), [ - 'name' => $name, - ])); + $team = $dbForInternal->updateDocument('teams', $team->getId(), $team->setAttribute('name', $name)); - if (false === $team) { - throw new Exception('Failed saving team to DB', 500); - } - - $response->dynamic($team, Response::MODEL_TEAM); + $response->dynamic2($team, Response::MODEL_TEAM); }); App::delete('/v1/teams/:teamId') @@ -210,40 +185,37 @@ App::delete('/v1/teams/:teamId') ->label('sdk.response.model', Response::MODEL_NONE) ->param('teamId', '', new UID(), 'Team unique ID.') ->inject('response') - ->inject('projectDB') + ->inject('dbForInternal') ->inject('events') - ->action(function ($teamId, $response, $projectDB, $events) { + ->action(function ($teamId, $response, $dbForInternal, $events) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Database $dbForInternal */ /** @var Appwrite\Event\Event $events */ - $team = $projectDB->getDocument($teamId); + $team = $dbForInternal->getDocument('teams', $teamId); - if (empty($team->getId()) || Database::SYSTEM_COLLECTION_TEAMS != $team->getCollection()) { + if (empty($team->getId())) { throw new Exception('Team not found', 404); } - $memberships = $projectDB->getCollection([ - 'limit' => 2000, // TODO add members limit - 'offset' => 0, - 'filters' => [ - '$collection='.Database::SYSTEM_COLLECTION_MEMBERSHIPS, - 'teamId='.$teamId, - ], - ]); + $memberships = $dbForInternal->find('memberships', [ + new Query('teamId', Query::TYPE_EQUAL, [$teamId]), + ], 2000, 0); // TODO fix members limit + + // TODO delete all members individually from the user object foreach ($memberships as $member) { - if (!$projectDB->deleteDocument($member->getId())) { + if (!$dbForInternal->deleteDocument('memberships', $member->getId())) { throw new Exception('Failed to remove membership for team from DB', 500); } } - if (!$projectDB->deleteDocument($teamId)) { + if (!$dbForInternal->deleteDocument('teams', $teamId)) { throw new Exception('Failed to remove team from DB', 500); } $events - ->setParam('eventData', $response->output($team, Response::MODEL_TEAM)) + ->setParam('eventData', $response->output2($team, Response::MODEL_TEAM)) ; $response->noContent(); @@ -271,15 +243,15 @@ App::post('/v1/teams/:teamId/memberships') ->inject('response') ->inject('project') ->inject('user') - ->inject('projectDB') + ->inject('dbForInternal') ->inject('locale') ->inject('audits') ->inject('mails') - ->action(function ($teamId, $email, $name, $roles, $url, $response, $project, $user, $projectDB, $locale, $audits, $mails) { + ->action(function ($teamId, $email, $name, $roles, $url, $response, $project, $user, $dbForInternal, $locale, $audits, $mails) { /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Document $project */ /** @var Appwrite\Database\Document $user */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Database $dbForInternal */ /** @var Appwrite\Event\Event $audits */ /** @var Appwrite\Event\Event $mails */ @@ -287,41 +259,21 @@ App::post('/v1/teams/:teamId/memberships') $isAppUser = Auth::isAppUser(Authorization::$roles); $name = (empty($name)) ? $email : $name; - $team = $projectDB->getDocument($teamId); + $team = $dbForInternal->getDocument('teams', $teamId); - if (empty($team->getId()) || Database::SYSTEM_COLLECTION_TEAMS != $team->getCollection()) { + if (empty($team->getId())) { throw new Exception('Team not found', 404); } - $memberships = $projectDB->getCollection([ - 'limit' => 50, - 'offset' => 0, - 'filters' => [ - '$collection='.Database::SYSTEM_COLLECTION_MEMBERSHIPS, - 'teamId='.$team->getId(), - ], - ]); - - $invitee = $projectDB->getCollectionFirst([ // Get user by email address - 'limit' => 1, - 'filters' => [ - '$collection='.Database::SYSTEM_COLLECTION_USERS, - 'email='.$email, - ], - ]); + $memberships = $dbForInternal->findFirst('memberships', [new Query('teamId', Query::TYPE_EQUAL, [$team->getId()])], 2000, 0); + $invitee = $dbForInternal->findFirst('users', [new Query('email', Query::TYPE_EQUAL, [$email])], 1); // Get user by email address if (empty($invitee)) { // Create new user if no user with same email found $limit = $project->getAttribute('usersAuthLimit', 0); if ($limit !== 0 && $project->getId() !== 'console') { // check users limit, console invites are allways allowed. - $projectDB->getCollection([ // Count users - 'filters' => [ - '$collection='.Database::SYSTEM_COLLECTION_USERS, - ], - ]); - - $sum = $projectDB->getSum(); + $sum = $dbForInternal->count('users'); // Count users TODO: add a 10k limit here. if($sum >= $limit) { throw new Exception('Project registration is restricted. Contact your administrator for more information.', 501); @@ -331,12 +283,11 @@ App::post('/v1/teams/:teamId/memberships') Authorization::disable(); try { - $invitee = $projectDB->createDocument([ - '$collection' => Database::SYSTEM_COLLECTION_USERS, - '$permissions' => [ - 'read' => ['user:{self}', '*'], - 'write' => ['user:{self}'], - ], + $userId = $dbForInternal->getId(); + $invitee = $dbForInternal->createDocument('users', new Document([ + '$id' => $userId, + '$read' => ['user:'.$userId, '*'], + '$write' => ['user:'.$userId], 'email' => $email, 'emailVerification' => false, 'status' => Auth::USER_STATUS_UNACTIVATED, @@ -347,16 +298,12 @@ App::post('/v1/teams/:teamId/memberships') 'name' => $name, 'sessions' => [], 'tokens' => [], - ], ['email' => $email]); + ])); } catch (Duplicate $th) { throw new Exception('Account already exists', 409); } Authorization::reset(); - - if (false === $invitee) { - throw new Exception('Failed saving user to DB', 500); - } } $isOwner = false; @@ -378,11 +325,9 @@ App::post('/v1/teams/:teamId/memberships') $secret = Auth::tokenGenerator(); $membership = new Document([ - '$collection' => Database::SYSTEM_COLLECTION_MEMBERSHIPS, - '$permissions' => [ - 'read' => ['*'], - 'write' => ['user:'.$invitee->getId(), 'team:'.$team->getId().'/owner'], - ], + '$id' => $dbForInternal->getId(), + '$read' => ['*'], + '$write' => ['user:'.$invitee->getId(), 'team:'.$team->getId().'/owner'], 'userId' => $invitee->getId(), 'teamId' => $team->getId(), 'roles' => $roles, @@ -394,28 +339,18 @@ App::post('/v1/teams/:teamId/memberships') if ($isPrivilegedUser || $isAppUser) { // Allow admin to create membership Authorization::disable(); - $membership = $projectDB->createDocument($membership->getArrayCopy()); + $membership = $dbForInternal->createDocument('memberships', $membership); - $team = $projectDB->updateDocument(\array_merge($team->getArrayCopy(), [ - 'sum' => $team->getAttribute('sum', 0) + 1, - ])); + $team = $dbForInternal->updateDocument('teams', $team->getId(), $team->setAttribute('sum', $team->getAttribute('sum', 0) + 1)); // Attach user to team $invitee->setAttribute('memberships', $membership, Document::SET_TYPE_APPEND); - $invitee = $projectDB->updateDocument($invitee->getArrayCopy()); - - if (false === $invitee) { - throw new Exception('Failed saving user to DB', 500); - } + $invitee = $dbForInternal->updateDocument('users', $invitee->getId(), $invitee); Authorization::reset(); } else { - $membership = $projectDB->createDocument($membership->getArrayCopy()); - } - - if (false === $membership) { - throw new Exception('Failed saving membership to DB', 500); + $membership = $dbForInternal->createDocument('memberships', $membership); } $url = Template::parseURL($url); @@ -462,13 +397,11 @@ App::post('/v1/teams/:teamId/memberships') ->setParam('resource', 'teams/'.$teamId) ; - $response - ->setStatusCode(Response::STATUS_CODE_CREATED) - ->dynamic(new Document(\array_merge($membership->getArrayCopy(), [ - 'email' => $email, - 'name' => $name, - ])), Response::MODEL_MEMBERSHIP) - ; + $response->setStatusCode(Response::STATUS_CODE_CREATED); + $response->dynamic2($membership + ->setAttribute('email', $email) + ->setAttribute('name', $name) + , Response::MODEL_MEMBERSHIP); }); App::get('/v1/teams/:teamId/memberships') @@ -488,27 +421,19 @@ App::get('/v1/teams/:teamId/memberships') ->param('offset', 0, new Range(0, 2000), 'Results offset. The default value is 0. Use this param to manage pagination.', true) ->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true) ->inject('response') - ->inject('projectDB') - ->action(function ($teamId, $search, $limit, $offset, $orderType, $response, $projectDB) { + ->inject('dbForInternal') + ->action(function ($teamId, $search, $limit, $offset, $orderType, $response, $dbForInternal) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Database $dbForInternal */ - $team = $projectDB->getDocument($teamId); + $team = $dbForInternal->getDocument('teams', $teamId); - if (empty($team->getId()) || Database::SYSTEM_COLLECTION_TEAMS != $team->getCollection()) { + if (empty($team->getId())) { throw new Exception('Team not found', 404); } - $memberships = $projectDB->getCollection([ - 'limit' => $limit, - 'offset' => $offset, - 'orderType' => $orderType, - 'search' => $search, - 'filters' => [ - '$collection='.Database::SYSTEM_COLLECTION_MEMBERSHIPS, - 'teamId='.$teamId, - ], - ]); + $memberships = $dbForInternal->find('memberships', [new Query('teamId', Query::TYPE_EQUAL, [$teamId])], $limit, $offset); + $sum = $dbForInternal->count('memberships', [new Query('teamId', Query::TYPE_EQUAL, [$teamId])], 5000); $users = []; foreach ($memberships as $membership) { @@ -516,12 +441,15 @@ App::get('/v1/teams/:teamId/memberships') continue; } - $temp = $projectDB->getDocument($membership->getAttribute('userId', null))->getArrayCopy(['email', 'name']); + $temp = $dbForInternal->getDocument('users', $membership->getAttribute('userId', null))->getArrayCopy(['email', 'name']); $users[] = new Document(\array_merge($temp, $membership->getArrayCopy())); } - $response->dynamic(new Document(['sum' => $projectDB->getSum(), 'memberships' => $users]), Response::MODEL_MEMBERSHIP_LIST); + $response->dynamic2(new Document([ + 'sum' => $sum, + 'memberships' => $users + ]), Response::MODEL_MEMBERSHIP_LIST); }); App::patch('/v1/teams/:teamId/memberships/:inviteId/status') @@ -543,21 +471,21 @@ App::patch('/v1/teams/:teamId/memberships/:inviteId/status') ->inject('request') ->inject('response') ->inject('user') - ->inject('projectDB') + ->inject('dbForInternal') ->inject('geodb') ->inject('audits') - ->action(function ($teamId, $inviteId, $userId, $secret, $request, $response, $user, $projectDB, $geodb, $audits) { + ->action(function ($teamId, $inviteId, $userId, $secret, $request, $response, $user, $dbForInternal, $geodb, $audits) { /** @var Utopia\Swoole\Request $request */ /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Document $user */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Database $dbForInternal */ /** @var MaxMind\Db\Reader $geodb */ /** @var Appwrite\Event\Event $audits */ $protocol = $request->getProtocol(); - $membership = $projectDB->getDocument($inviteId); + $membership = $dbForInternal->getDocument('memberships', $inviteId); - if (empty($membership->getId()) || Database::SYSTEM_COLLECTION_MEMBERSHIPS != $membership->getCollection()) { + if (empty($membership->getId())) { throw new Exception('Invite not found', 404); } @@ -567,11 +495,11 @@ App::patch('/v1/teams/:teamId/memberships/:inviteId/status') Authorization::disable(); - $team = $projectDB->getDocument($teamId); + $team = $dbForInternal->getDocument('teams', $teamId); Authorization::reset(); - if (empty($team->getId()) || Database::SYSTEM_COLLECTION_TEAMS != $team->getCollection()) { + if (empty($team->getId())) { throw new Exception('Team not found', 404); } @@ -584,13 +512,7 @@ App::patch('/v1/teams/:teamId/memberships/:inviteId/status') } if (empty($user->getId())) { - $user = $projectDB->getCollectionFirst([ // Get user - 'limit' => 1, - 'filters' => [ - '$collection='.Database::SYSTEM_COLLECTION_USERS, - '$id='.$userId, - ], - ]); + $user = $dbForInternal->getDocument('users', $userId); // Get user } if ($membership->getAttribute('userId') !== $user->getId()) { @@ -614,8 +536,7 @@ App::patch('/v1/teams/:teamId/memberships/:inviteId/status') $expiry = \time() + Auth::TOKEN_EXPIRATION_LOGIN_LONG; $secret = Auth::tokenGenerator(); $session = new Document(array_merge([ - '$collection' => Database::SYSTEM_COLLECTION_SESSIONS, - '$permissions' => ['read' => ['user:'.$user->getId()], 'write' => ['user:'.$user->getId()]], + '$id' => $dbForInternal->getId(), 'userId' => $user->getId(), 'provider' => Auth::SESSION_PROVIDER_EMAIL, 'providerUid' => $user->getAttribute('email'), @@ -630,24 +551,14 @@ App::patch('/v1/teams/:teamId/memberships/:inviteId/status') Authorization::setRole('user:'.$userId); - $user = $projectDB->updateDocument($user->getArrayCopy()); - - if (false === $user) { - throw new Exception('Failed saving user to DB', 500); - } + $user = $dbForInternal->updateDocument('users', $user->getId(), $user); Authorization::disable(); - $team = $projectDB->updateDocument(\array_merge($team->getArrayCopy(), [ - 'sum' => $team->getAttribute('sum', 0) + 1, - ])); + $team = $dbForInternal->updateDocument('teams', $team->getId(), $team->setAttribute('sum', $team->getAttribute('sum', 0) + 1)); Authorization::reset(); - if (false === $team) { - throw new Exception('Failed saving team to DB', 500); - } - $audits ->setParam('userId', $user->getId()) ->setParam('event', 'teams.membership.update') @@ -665,10 +576,10 @@ App::patch('/v1/teams/:teamId/memberships/:inviteId/status') ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), $expiry, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) ; - $response->dynamic(new Document(\array_merge($membership->getArrayCopy(), [ - 'email' => $user->getAttribute('email'), - 'name' => $user->getAttribute('name'), - ])), Response::MODEL_MEMBERSHIP); + $response->dynamic2($membership + ->setAttribute('email', $user->getAttribute('email')) + ->setAttribute('name', $user->getAttribute('name')) + , Response::MODEL_MEMBERSHIP); }); App::delete('/v1/teams/:teamId/memberships/:inviteId') @@ -685,18 +596,18 @@ App::delete('/v1/teams/:teamId/memberships/:inviteId') ->param('teamId', '', new UID(), 'Team unique ID.') ->param('inviteId', '', new UID(), 'Invite unique ID.') ->inject('response') - ->inject('projectDB') + ->inject('dbForInternal') ->inject('audits') ->inject('events') - ->action(function ($teamId, $inviteId, $response, $projectDB, $audits, $events) { + ->action(function ($teamId, $inviteId, $response, $dbForInternal, $audits, $events) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Database $dbForInternal */ /** @var Appwrite\Event\Event $audits */ /** @var Appwrite\Event\Event $events */ - $membership = $projectDB->getDocument($inviteId); + $membership = $dbForInternal->getDocument('memberships', $inviteId); - if (empty($membership->getId()) || Database::SYSTEM_COLLECTION_MEMBERSHIPS != $membership->getCollection()) { + if (empty($membership->getId())) { throw new Exception('Invite not found', 404); } @@ -704,24 +615,18 @@ App::delete('/v1/teams/:teamId/memberships/:inviteId') throw new Exception('Team IDs don\'t match', 404); } - $team = $projectDB->getDocument($teamId); + $team = $dbForInternal->getDocument('teams', $teamId); - if (empty($team->getId()) || Database::SYSTEM_COLLECTION_TEAMS != $team->getCollection()) { + if (empty($team->getId())) { throw new Exception('Team not found', 404); } - if (!$projectDB->deleteDocument($membership->getId())) { + if (!$dbForInternal->deleteDocument('memberships', $membership->getId())) { throw new Exception('Failed to remove membership from DB', 500); } if ($membership->getAttribute('confirm')) { // Count only confirmed members - $team = $projectDB->updateDocument(\array_merge($team->getArrayCopy(), [ - 'sum' => $team->getAttribute('sum', 0) - 1, - ])); - } - - if (false === $team) { - throw new Exception('Failed saving team to DB', 500); + $team = $dbForInternal->updateDocument('teams', $team->getId(), $team->setAttribute('sum', $team->getAttribute('sum', 0) - 1)); } $audits @@ -731,7 +636,7 @@ App::delete('/v1/teams/:teamId/memberships/:inviteId') ; $events - ->setParam('eventData', $response->output($membership, Response::MODEL_MEMBERSHIP)) + ->setParam('eventData', $response->output2($membership, Response::MODEL_MEMBERSHIP)) ; $response->noContent(); diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 6b5bfbdbd9..82386f44f7 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -1,5 +1,8 @@ param('password', '', new Password(), 'User password. Must be between 6 to 32 chars.') ->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true) ->inject('response') - ->inject('projectDB') - ->action(function ($email, $password, $name, $response, $projectDB) { + ->inject('dbForInternal') + ->action(function ($email, $password, $name, $response, $dbForInternal) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $projectDB */ - - $profile = $projectDB->getCollectionFirst([ // Get user by email address - 'limit' => 1, - 'filters' => [ - '$collection='.Database::SYSTEM_COLLECTION_USERS, - 'email='.$email, - ], - ]); - - if (!empty($profile)) { - throw new Exception('User already registered', 409); - } + /** @var Utopia\Database\Database $dbForInternal */ try { - $user = $projectDB->createDocument([ - '$collection' => Database::SYSTEM_COLLECTION_USERS, - '$permissions' => [ - 'read' => ['*'], - 'write' => ['user:{self}'], - ], + $userId = $dbForInternal->getId(); + $user = $dbForInternal->createDocument('users', new Document([ + '$id' => $userId, + '$read' => ['*'], + '$write' => ['user:'.$userId], 'email' => $email, 'emailVerification' => false, 'status' => Auth::USER_STATUS_UNACTIVATED, @@ -66,15 +52,17 @@ App::post('/v1/users') 'registration' => \time(), 'reset' => false, 'name' => $name, - ], ['email' => $email]); + 'prefs' => [], + 'sessions' => [], + 'tokens' => [], + 'memberships' => [], + ])); } catch (Duplicate $th) { throw new Exception('Account already exists', 409); } - $response - ->setStatusCode(Response::STATUS_CODE_CREATED) - ->dynamic($user, Response::MODEL_USER) - ; + $response->setStatusCode(Response::STATUS_CODE_CREATED); + $response->dynamic2($user, Response::MODEL_USER); }); App::get('/v1/users') @@ -93,23 +81,16 @@ App::get('/v1/users') ->param('offset', 0, new Range(0, 2000), 'Results offset. The default value is 0. Use this param to manage pagination.', true) ->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true) ->inject('response') - ->inject('projectDB') - ->action(function ($search, $limit, $offset, $orderType, $response, $projectDB) { + ->inject('dbForInternal') + ->action(function ($search, $limit, $offset, $orderType, $response, $dbForInternal) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Database $dbForInternal */ - $results = $projectDB->getCollection([ - 'limit' => $limit, - 'offset' => $offset, - 'orderType' => $orderType, - 'search' => $search, - 'filters' => [ - '$collection='.Database::SYSTEM_COLLECTION_USERS, - ], - ]); + $results = $dbForInternal->find('users', [], $limit, $offset); + $sum = $dbForInternal->count('users', [], 5000); - $response->dynamic(new Document([ - 'sum' => $projectDB->getSum(), + $response->dynamic2(new Document([ + 'sum' => $sum, 'users' => $results ]), Response::MODEL_USER_LIST); }); @@ -127,18 +108,18 @@ App::get('/v1/users/:userId') ->label('sdk.response.model', Response::MODEL_USER) ->param('userId', '', new UID(), 'User unique ID.') ->inject('response') - ->inject('projectDB') - ->action(function ($userId, $response, $projectDB) { + ->inject('dbForInternal') + ->action(function ($userId, $response, $dbForInternal) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Database $dbForInternal */ - $user = $projectDB->getDocument($userId); + $user = $dbForInternal->getDocument('users', $userId); - if (empty($user->getId()) || Database::SYSTEM_COLLECTION_USERS != $user->getCollection()) { + if (empty($user->getId())) { throw new Exception('User not found', 404); } - $response->dynamic($user, Response::MODEL_USER); + $response->dynamic2($user, Response::MODEL_USER); }); App::get('/v1/users/:userId/prefs') @@ -154,20 +135,20 @@ App::get('/v1/users/:userId/prefs') ->label('sdk.response.model', Response::MODEL_PREFERENCES) ->param('userId', '', new UID(), 'User unique ID.') ->inject('response') - ->inject('projectDB') - ->action(function ($userId, $response, $projectDB) { + ->inject('dbForInternal') + ->action(function ($userId, $response, $dbForInternal) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Database $dbForInternal */ - $user = $projectDB->getDocument($userId); + $user = $dbForInternal->getDocument('users', $userId); - if (empty($user->getId()) || Database::SYSTEM_COLLECTION_USERS != $user->getCollection()) { + if (empty($user->getId())) { throw new Exception('User not found', 404); } $prefs = $user->getAttribute('prefs', new \stdClass()); - $response->dynamic(new Document($prefs), Response::MODEL_PREFERENCES); + $response->dynamic2(new Document($prefs), Response::MODEL_PREFERENCES); }); App::get('/v1/users/:userId/sessions') @@ -183,16 +164,16 @@ App::get('/v1/users/:userId/sessions') ->label('sdk.response.model', Response::MODEL_SESSION_LIST) ->param('userId', '', new UID(), 'User unique ID.') ->inject('response') - ->inject('projectDB') + ->inject('dbForInternal') ->inject('locale') - ->action(function ($userId, $response, $projectDB, $locale) { + ->action(function ($userId, $response, $dbForInternal, $locale) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Database $dbForInternal */ /** @var Utopia\Locale\Locale $locale */ - $user = $projectDB->getDocument($userId); + $user = $dbForInternal->getDocument('users', $userId); - if (empty($user->getId()) || Database::SYSTEM_COLLECTION_USERS != $user->getCollection()) { + if (empty($user->getId())) { throw new Exception('User not found', 404); } @@ -210,11 +191,11 @@ App::get('/v1/users/:userId/sessions') $sessions[$key] = $session; } - $response->dynamic(new Document([ + $response->dynamic2(new Document([ 'sum' => count($sessions), 'sessions' => $sessions ]), Response::MODEL_SESSION_LIST); - }, ['response', 'projectDB', 'locale']); + }, ['response', 'dbForInternal', 'locale']); App::get('/v1/users/:userId/logs') ->desc('Get User Logs') @@ -231,20 +212,20 @@ App::get('/v1/users/:userId/logs') ->inject('response') ->inject('register') ->inject('project') - ->inject('projectDB') + ->inject('dbForInternal') ->inject('locale') ->inject('geodb') - ->action(function ($userId, $response, $register, $project, $projectDB, $locale, $geodb) { + ->action(function ($userId, $response, $register, $project, $dbForInternal, $locale, $geodb) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Registry\Registry $register */ /** @var Appwrite\Database\Document $project */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Database $dbForInternal */ /** @var Utopia\Locale\Locale $locale */ /** @var MaxMind\Db\Reader $geodb */ - $user = $projectDB->getDocument($userId); + $user = $dbForInternal->getDocument('users', $userId); - if (empty($user->getId()) || Database::SYSTEM_COLLECTION_USERS != $user->getCollection()) { + if (empty($user->getId())) { throw new Exception('User not found', 404); } @@ -327,7 +308,7 @@ App::get('/v1/users/:userId/logs') } } - $response->dynamic(new Document(['logs' => $output]), Response::MODEL_LOG_LIST); + $response->dynamic2(new Document(['logs' => $output]), Response::MODEL_LOG_LIST); }); App::patch('/v1/users/:userId/status') @@ -345,26 +326,20 @@ App::patch('/v1/users/:userId/status') ->param('userId', '', new UID(), 'User unique ID.') ->param('status', '', new WhiteList([Auth::USER_STATUS_ACTIVATED, Auth::USER_STATUS_BLOCKED, Auth::USER_STATUS_UNACTIVATED], true), 'User Status code. To activate the user pass '.Auth::USER_STATUS_ACTIVATED.', to block the user pass '.Auth::USER_STATUS_BLOCKED.' and for disabling the user pass '.Auth::USER_STATUS_UNACTIVATED) ->inject('response') - ->inject('projectDB') - ->action(function ($userId, $status, $response, $projectDB) { + ->inject('dbForInternal') + ->action(function ($userId, $status, $response, $dbForInternal) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Database $dbForInternal */ - $user = $projectDB->getDocument($userId); + $user = $dbForInternal->getDocument('users', $userId); - if (empty($user->getId()) || Database::SYSTEM_COLLECTION_USERS != $user->getCollection()) { + if (empty($user->getId())) { throw new Exception('User not found', 404); } - $user = $projectDB->updateDocument(\array_merge($user->getArrayCopy(), [ - 'status' => (int)$status, - ])); + $user = $dbForInternal->updateDocument('users', $user->getId(), $user->setAttribute('status', (int)$status)); - if (false === $user) { - throw new Exception('Failed saving user to DB', 500); - } - - $response->dynamic($user, Response::MODEL_USER); + $response->dynamic2($user, Response::MODEL_USER); }); App::patch('/v1/users/:userId/prefs') @@ -382,26 +357,20 @@ App::patch('/v1/users/:userId/prefs') ->param('userId', '', new UID(), 'User unique ID.') ->param('prefs', '', new Assoc(), 'Prefs key-value JSON object.') ->inject('response') - ->inject('projectDB') - ->action(function ($userId, $prefs, $response, $projectDB) { + ->inject('dbForInternal') + ->action(function ($userId, $prefs, $response, $dbForInternal) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Database $dbForInternal */ - $user = $projectDB->getDocument($userId); + $user = $dbForInternal->getDocument('users', $userId); - if (empty($user->getId()) || Database::SYSTEM_COLLECTION_USERS != $user->getCollection()) { + if ($user->isEmpty()) { throw new Exception('User not found', 404); } - $user = $projectDB->updateDocument(\array_merge($user->getArrayCopy(), [ - 'prefs' => $prefs, - ])); + $user = $dbForInternal->updateDocument('users', $user->getId(), $user->setAttribute('prefs', $prefs)); - if (false === $user) { - throw new Exception('Failed saving user to DB', 500); - } - - $response->dynamic(new Document($prefs), Response::MODEL_PREFERENCES); + $response->dynamic2(new Document($prefs), Response::MODEL_PREFERENCES); }); App::delete('/v1/users/:userId/sessions/:sessionId') @@ -418,32 +387,34 @@ App::delete('/v1/users/:userId/sessions/:sessionId') ->param('userId', '', new UID(), 'User unique ID.') ->param('sessionId', null, new UID(), 'User unique session ID.') ->inject('response') - ->inject('projectDB') + ->inject('dbForInternal') ->inject('events') - ->action(function ($userId, $sessionId, $response, $projectDB, $events) { + ->action(function ($userId, $sessionId, $response, $dbForInternal, $events) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Database $dbForInternal */ /** @var Appwrite\Event\Event $events */ - $user = $projectDB->getDocument($userId); + $user = $dbForInternal->getDocument('users', $userId); - if (empty($user->getId()) || Database::SYSTEM_COLLECTION_USERS != $user->getCollection()) { + if (empty($user->getId())) { throw new Exception('User not found', 404); } $sessions = $user->getAttribute('sessions', []); - foreach ($sessions as $session) { + foreach ($sessions as $key => $session) { /** @var Document $session */ if ($sessionId == $session->getId()) { - if (!$projectDB->deleteDocument($session->getId())) { - throw new Exception('Failed to remove token from DB', 500); - } + unset($sessions[$key]); + $user->setAttribute('sessions', $sessions); + $events - ->setParam('eventData', $response->output($user, Response::MODEL_USER)) + ->setParam('eventData', $response->output2($user, Response::MODEL_USER)) ; + + $dbForInternal->updateDocument('users', $user->getId(), $user); } } @@ -464,31 +435,25 @@ App::delete('/v1/users/:userId/sessions') ->label('sdk.response.model', Response::MODEL_NONE) ->param('userId', '', new UID(), 'User unique ID.') ->inject('response') - ->inject('projectDB') + ->inject('dbForInternal') ->inject('events') - ->action(function ($userId, $response, $projectDB, $events) { + ->action(function ($userId, $response, $dbForInternal, $events) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Database $dbForInternal */ /** @var Appwrite\Event\Event $events */ - $user = $projectDB->getDocument($userId); + $user = $dbForInternal->getDocument('users', $userId); - if (empty($user->getId()) || Database::SYSTEM_COLLECTION_USERS != $user->getCollection()) { + if (empty($user->getId())) { throw new Exception('User not found', 404); } $sessions = $user->getAttribute('sessions', []); - - foreach ($sessions as $session) { - /** @var Document $session */ - - if (!$projectDB->deleteDocument($session->getId())) { - throw new Exception('Failed to remove token from DB', 500); - } - } + + $dbForInternal->updateDocument('users', $user->getId(), $user); $events - ->setParam('eventData', $response->output($user, Response::MODEL_USER)) + ->setParam('eventData', $response->output2($user, Response::MODEL_USER)) ; // TODO : Response filter implementation @@ -508,39 +473,29 @@ App::delete('/v1/users/:userId') ->label('sdk.response.model', Response::MODEL_NONE) ->param('userId', '', function () {return new UID();}, 'User unique ID.') ->inject('response') - ->inject('projectDB') + ->inject('dbForInternal') ->inject('events') ->inject('deletes') - ->action(function ($userId, $response, $projectDB, $events, $deletes) { + ->action(function ($userId, $response, $dbForInternal, $events, $deletes) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Database $dbForInternal */ /** @var Appwrite\Event\Event $events */ /** @var Appwrite\Event\Event $deletes */ - $user = $projectDB->getDocument($userId); + $user = $dbForInternal->getDocument('users', $userId); - if (empty($user->getId()) || Database::SYSTEM_COLLECTION_USERS != $user->getCollection()) { + if ($user->isEmpty()) { throw new Exception('User not found', 404); } - if (!$projectDB->deleteDocument($userId)) { + + if (!$dbForInternal->deleteDocument('users', $userId)) { throw new Exception('Failed to remove user from DB', 500); } - - if (!$projectDB->deleteUniqueKey(md5('users:email='.$user->getAttribute('email', null)))) { - throw new Exception('Failed to remove unique key from DB', 500); - } - $reservedId = $projectDB->createDocument([ - '$collection' => Database::SYSTEM_COLLECTION_RESERVED, - '$id' => $userId, - '$permissions' => [ - 'read' => ['*'], - ], - ]); - - if (false === $reservedId) { - throw new Exception('Failed saving reserved id to DB', 500); - } + // $dbForInternal->createDocument('users', new Document([ + // '$id' => $userId, + // '$read' => ['*'], + // ])); $deletes ->setParam('type', DELETE_TYPE_DOCUMENT) @@ -548,7 +503,7 @@ App::delete('/v1/users/:userId') ; $events - ->setParam('eventData', $response->output($user, Response::MODEL_USER)) + ->setParam('eventData', $response->output2($user, Response::MODEL_USER)) ; // TODO : Response filter implementation diff --git a/app/init.php b/app/init.php index 7a163a39cb..4f599aa45b 100644 --- a/app/init.php +++ b/app/init.php @@ -406,12 +406,12 @@ App::setResource('clients', function($request, $console, $project) { return $clients; }, ['request', 'console', 'project']); -App::setResource('user', function($mode, $project, $console, $request, $response, $projectDB, $consoleDB) { +App::setResource('user', function($mode, $project, $console, $request, $response, $dbForInternal, $dbForConsole) { /** @var Utopia\Swoole\Request $request */ /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Document $project */ - /** @var Appwrite\Database\Database $consoleDB */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Document $project */ + /** @var Utopia\Database\Database $dbForInternal */ + /** @var Utopia\Database\Database $dbForConsole */ /** @var bool $mode */ Authorization::setDefaultStatus(true); @@ -437,14 +437,14 @@ App::setResource('user', function($mode, $project, $console, $request, $response $session = Auth::decodeSession(((isset($fallback[Auth::$cookieName])) ? $fallback[Auth::$cookieName] : '')); } - Auth::$unique = $session['id']; - Auth::$secret = $session['secret']; + Auth::$unique = $session['id'] ?? ''; + Auth::$secret = $session['secret'] ?? ''; if (APP_MODE_ADMIN !== $mode) { - $user = $projectDB->getDocument(Auth::$unique); + $user = $dbForInternal->getDocument('users', Auth::$unique); } else { - $user = $consoleDB->getDocument(Auth::$unique); + $user = $dbForConsole->getDocument('users', Auth::$unique); $user ->setAttribute('$id', 'admin-'.$user->getAttribute('$id')) @@ -481,7 +481,7 @@ App::setResource('user', function($mode, $project, $console, $request, $response $jwtSessionId = $payload['sessionId'] ?? ''; if($jwtUserId && $jwtSessionId) { - $user = $projectDB->getDocument($jwtUserId); + $user = $dbForInternal->getDocument('users', $jwtUserId); } if (empty($user->search('$id', $jwtSessionId, $user->getAttribute('tokens')))) { // Match JWT to active token @@ -490,7 +490,7 @@ App::setResource('user', function($mode, $project, $console, $request, $response } return $user; -}, ['mode', 'project', 'console', 'request', 'response', 'projectDB', 'consoleDB']); +}, ['mode', 'project', 'console', 'request', 'response', 'dbForInternal', 'dbForConsole']); App::setResource('project', function($consoleDB, $request) { /** @var Utopia\Swoole\Request $request */ @@ -500,7 +500,7 @@ App::setResource('project', function($consoleDB, $request) { Authorization2::disable(); $project = $consoleDB->getDocument($request->getParam('project', - $request->getHeader('x-appwrite-project', ''))); + $request->getHeader('x-appwrite-project', 'console'))); Authorization::reset(); Authorization2::reset(); From dc57a41f369ee6d0fac39655f20e1c4b6ff70b3d Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sun, 9 May 2021 02:10:13 +0300 Subject: [PATCH 22/60] Fixed account tests --- app/controllers/api/account.php | 10 +++++++++- app/init.php | 4 ++-- composer.lock | 4 ++-- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index c7c8fe3ffc..d8e0ac3bfe 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -484,6 +484,10 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') 'registration' => \time(), 'reset' => false, 'name' => $name, + 'prefs' => [], + 'sessions' => [], + 'tokens' => [], + 'memberships' => [], ])); } catch (Duplicate $th) { throw new Exception('Account already exists', 409); @@ -638,7 +642,11 @@ App::post('/v1/account/sessions/anonymous') 'passwordUpdate' => \time(), 'registration' => \time(), 'reset' => false, - 'name' => null + 'name' => null, + 'prefs' => [], + 'sessions' => [], + 'tokens' => [], + 'memberships' => [], ])); } catch (Exception $th) { throw new Exception('Failed saving user to DB', 500); diff --git a/app/init.php b/app/init.php index 4a346a3efb..93ca871e91 100644 --- a/app/init.php +++ b/app/init.php @@ -485,8 +485,8 @@ App::setResource('user', function($mode, $project, $console, $request, $response $user = $dbForInternal->getDocument('users', $jwtUserId); } - if (empty($user->search('$id', $jwtSessionId, $user->getAttribute('tokens')))) { // Match JWT to active token - $user = new Document(['$id' => '', '$collection' => Database::SYSTEM_COLLECTION_USERS]); + if (empty($user->search('$id', $jwtSessionId, $user->getAttribute('sessions')))) { // Match JWT to active token + $user = new Document(['$id' => '', '$collection' => 'users']); } } diff --git a/composer.lock b/composer.lock index 9537c83569..ef8f6ed820 100644 --- a/composer.lock +++ b/composer.lock @@ -1923,7 +1923,7 @@ "source": { "type": "git", "url": "https://github.com/utopia-php/database", - "reference": "11a149d2fd04b73c9fddffb93e96efc78d4a6039" + "reference": "20d189c6687ebfe82c3148794a01f51ebc6efb05" }, "require": { "ext-mongodb": "*", @@ -1970,7 +1970,7 @@ "upf", "utopia" ], - "time": "2021-05-06T20:03:31+00:00" + "time": "2021-05-08T22:11:07+00:00" }, { "name": "utopia-php/domains", From c173d4a2e38ec0e89939fda85143679b4225d011 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sun, 9 May 2021 21:37:47 +0300 Subject: [PATCH 23/60] More cometics --- app/controllers/api/account.php | 6 +-- app/controllers/api/functions.php | 6 +-- app/controllers/api/storage.php | 2 +- app/controllers/api/teams.php | 43 +++++++++----------- app/controllers/api/users.php | 2 +- app/init.php | 2 + composer.lock | 4 +- docker-compose.yml | 2 +- tests/e2e/Services/Teams/TeamsBaseClient.php | 13 ++++++ tests/e2e/Services/Teams/TeamsBaseServer.php | 13 ++++++ 10 files changed, 59 insertions(+), 34 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index d8e0ac3bfe..7750980ab8 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -79,7 +79,7 @@ App::post('/v1/account') $limit = $project->getAttribute('usersAuthLimit', 0); if ($limit !== 0) { - $sum = $dbForInternal->count('users'); // Count users TODO: add a 10k limit here. + $sum = $dbForInternal->count('users', [], APP_LIMIT_USERS); if($sum >= $limit) { throw new Exception('Project registration is restricted. Contact your administrator for more information.', 501); @@ -461,7 +461,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') $limit = $project->getAttribute('usersAuthLimit', 0); if ($limit !== 0) { - $sum = $dbForInternal->count('users'); // Count users TODO: add a 10k limit here. + $sum = $dbForInternal->count('users', [], APP_LIMIT_COUNT); if($sum >= $limit) { throw new Exception('Project registration is restricted. Contact your administrator for more information.', 501); @@ -621,7 +621,7 @@ App::post('/v1/account/sessions/anonymous') $limit = $project->getAttribute('usersAuthLimit', 0); if ($limit !== 0) { - $sum = $dbForInternal->count('users'); // Count users TODO: add a 10k limit here. + $sum = $dbForInternal->count('users', [], APP_LIMIT_COUNT); if($sum >= $limit) { throw new Exception('Project registration is restricted. Contact your administrator for more information.', 501); diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index 425c9b1127..1b8c78c5ee 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -98,7 +98,7 @@ App::get('/v1/functions') /** @var Utopia\Database\Database $dbForInternal */ $response->dynamic2(new Document([ - 'sum' => $dbForInternal->count('functions', [], 5000), + 'sum' => $dbForInternal->count('functions', [], APP_LIMIT_COUNT), 'functions' => $dbForInternal->find('functions', [], $limit, $offset) ]), Response::MODEL_FUNCTION_LIST); }); @@ -537,7 +537,7 @@ App::get('/v1/functions/:functionId/tags') $sum = $dbForInternal->count('tags', [ new Query('functionId', Query::TYPE_EQUAL, [$function->getId()]), - ], 5000); + ], APP_LIMIT_COUNT); $response->dynamic2(new Document([ 'sum' => $sum, @@ -793,7 +793,7 @@ App::get('/v1/functions/:functionId/executions') $sum = $dbForInternal->count('executions', [ new Query('functionId', Query::TYPE_EQUAL, [$function->getId()]), - ], 5000); + ], APP_LIMIT_COUNT); $response->dynamic2(new Document([ 'sum' => $sum, diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index d90a467026..5dda3f1fe7 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -180,7 +180,7 @@ App::get('/v1/storage/files') /** @var Utopia\Database\Database $dbForInternal */ $response->dynamic2(new Document([ - 'sum' => $dbForInternal->count('files', [], 5000), + 'sum' => $dbForInternal->count('files', [], APP_LIMIT_COUNT), 'files' => $dbForInternal->find('files', [], $limit, $offset) ]), Response::MODEL_FILE_LIST); }); diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index bc6ae280bd..fffb8b2ebb 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -50,7 +50,7 @@ App::post('/v1/teams') $teamId = $dbForInternal->getId(); $team = $dbForInternal->createDocument('teams', new Document([ '$id' => $teamId , - '$read' => ['team:'.$teamId ], + '$read' => ['team:'.$teamId], '$write' => ['team:'.$teamId .'/owner'], 'name' => $name, 'sum' => ($isPrivilegedUser || $isAppUser) ? 0 : 1, @@ -76,7 +76,6 @@ App::post('/v1/teams') // Attach user to team $user->setAttribute('memberships', $membership, Document::SET_TYPE_APPEND); - $user = $dbForInternal->updateDocument('users', $user->getId(), $user); } @@ -106,7 +105,7 @@ App::get('/v1/teams') /** @var Utopia\Database\Database $dbForInternal */ $results = $dbForInternal->find('teams', [], $limit, $offset); - $sum = $dbForInternal->count('teams', [], 5000); + $sum = $dbForInternal->count('teams', [], APP_LIMIT_COUNT); $response->dynamic2(new Document([ 'sum' => $sum, @@ -203,9 +202,8 @@ App::delete('/v1/teams/:teamId') ], 2000, 0); // TODO fix members limit // TODO delete all members individually from the user object - - foreach ($memberships as $member) { - if (!$dbForInternal->deleteDocument('memberships', $member->getId())) { + foreach ($memberships as $membership) { + if (!$dbForInternal->deleteDocument('memberships', $membership->getId())) { throw new Exception('Failed to remove membership for team from DB', 500); } } @@ -265,7 +263,6 @@ App::post('/v1/teams/:teamId/memberships') throw new Exception('Team not found', 404); } - $memberships = $dbForInternal->findFirst('memberships', [new Query('teamId', Query::TYPE_EQUAL, [$team->getId()])], 2000, 0); $invitee = $dbForInternal->findFirst('users', [new Query('email', Query::TYPE_EQUAL, [$email])], 1); // Get user by email address if (empty($invitee)) { // Create new user if no user with same email found @@ -273,7 +270,7 @@ App::post('/v1/teams/:teamId/memberships') $limit = $project->getAttribute('usersAuthLimit', 0); if ($limit !== 0 && $project->getId() !== 'console') { // check users limit, console invites are allways allowed. - $sum = $dbForInternal->count('users'); // Count users TODO: add a 10k limit here. + $sum = $dbForInternal->count('users', [], APP_LIMIT_USERS); if($sum >= $limit) { throw new Exception('Project registration is restricted. Contact your administrator for more information.', 501); @@ -296,8 +293,10 @@ App::post('/v1/teams/:teamId/memberships') 'registration' => \time(), 'reset' => false, 'name' => $name, + 'prefs' => [], 'sessions' => [], 'tokens' => [], + 'memberships' => [], ])); } catch (Duplicate $th) { throw new Exception('Account already exists', 409); @@ -306,17 +305,7 @@ App::post('/v1/teams/:teamId/memberships') Authorization::reset(); } - $isOwner = false; - - foreach ($memberships as $member) { - if ($member->getAttribute('userId') == $invitee->getId()) { - throw new Exception('User has already been invited or is already a member of this team', 409); - } - - if ($member->getAttribute('userId') == $user->getId() && \in_array('owner', $member->getAttribute('roles', []))) { - $isOwner = true; - } - } + $isOwner = Authorization::isRole('team:'.$team->getId().'/owner');; if (!$isOwner && !$isPrivilegedUser && !$isAppUser) { // Not owner, not admin, not app (server) throw new Exception('User is not allowed to send invitations for this team', 401); @@ -339,7 +328,11 @@ App::post('/v1/teams/:teamId/memberships') if ($isPrivilegedUser || $isAppUser) { // Allow admin to create membership Authorization::disable(); - $membership = $dbForInternal->createDocument('memberships', $membership); + try { + $membership = $dbForInternal->createDocument('memberships', $membership); + } catch (Duplicate $th) { + throw new Exception('User has already been invited or is already a member of this team', 409); + } $team = $dbForInternal->updateDocument('teams', $team->getId(), $team->setAttribute('sum', $team->getAttribute('sum', 0) + 1)); @@ -350,7 +343,11 @@ App::post('/v1/teams/:teamId/memberships') Authorization::reset(); } else { - $membership = $dbForInternal->createDocument('memberships', $membership); + try { + $membership = $dbForInternal->createDocument('memberships', $membership); + } catch (Duplicate $th) { + throw new Exception('User has already been invited or is already a member of this team', 409); + } } $url = Template::parseURL($url); @@ -433,7 +430,7 @@ App::get('/v1/teams/:teamId/memberships') } $memberships = $dbForInternal->find('memberships', [new Query('teamId', Query::TYPE_EQUAL, [$teamId])], $limit, $offset); - $sum = $dbForInternal->count('memberships', [new Query('teamId', Query::TYPE_EQUAL, [$teamId])], 5000); + $sum = $dbForInternal->count('memberships', [new Query('teamId', Query::TYPE_EQUAL, [$teamId])], APP_LIMIT_COUNT); $users = []; foreach ($memberships as $membership) { @@ -486,7 +483,7 @@ App::patch('/v1/teams/:teamId/memberships/:inviteId/status') $membership = $dbForInternal->getDocument('memberships', $inviteId); if (empty($membership->getId())) { - throw new Exception('Invite not found', 404); + throw new Exception('Membership not found', 404); } if ($membership->getAttribute('teamId') !== $teamId) { diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 82386f44f7..37e6d911d3 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -87,7 +87,7 @@ App::get('/v1/users') /** @var Utopia\Database\Database $dbForInternal */ $results = $dbForInternal->find('users', [], $limit, $offset); - $sum = $dbForInternal->count('users', [], 5000); + $sum = $dbForInternal->count('users', [], APP_LIMIT_COUNT); $response->dynamic2(new Document([ 'sum' => $sum, diff --git a/app/init.php b/app/init.php index 93ca871e91..8f11d0bc02 100644 --- a/app/init.php +++ b/app/init.php @@ -44,6 +44,8 @@ const APP_USERAGENT = APP_NAME.'-Server v%s. Please report abuse at %s'; const APP_MODE_DEFAULT = 'default'; const APP_MODE_ADMIN = 'admin'; const APP_PAGING_LIMIT = 12; +const APP_LIMIT_COUNT = 5000; +const APP_LIMIT_USERS = 10000; const APP_CACHE_BUSTER = 145; const APP_VERSION_STABLE = '0.8.0'; const APP_STORAGE_UPLOADS = '/storage/uploads'; diff --git a/composer.lock b/composer.lock index ef8f6ed820..8dbf102ab0 100644 --- a/composer.lock +++ b/composer.lock @@ -1923,7 +1923,7 @@ "source": { "type": "git", "url": "https://github.com/utopia-php/database", - "reference": "20d189c6687ebfe82c3148794a01f51ebc6efb05" + "reference": "d54bbae93fea9f60df535e4f669146e24d87761b" }, "require": { "ext-mongodb": "*", @@ -1970,7 +1970,7 @@ "upf", "utopia" ], - "time": "2021-05-08T22:11:07+00:00" + "time": "2021-05-09T13:42:23+00:00" }, { "name": "utopia-php/domains", diff --git a/docker-compose.yml b/docker-compose.yml index bdd7f59966..5d7604b7ef 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -63,7 +63,7 @@ services: - ./psalm.xml:/usr/src/code/psalm.xml - ./tests:/usr/src/code/tests - ./app:/usr/src/code/app - # - ./vendor:/usr/src/code/vendor + - ./vendor/utopia-php/database:/usr/src/code/vendor/utopia-php/database - ./docs:/usr/src/code/docs - ./public:/usr/src/code/public - ./src:/usr/src/code/src diff --git a/tests/e2e/Services/Teams/TeamsBaseClient.php b/tests/e2e/Services/Teams/TeamsBaseClient.php index 72fc99d881..702e541ae1 100644 --- a/tests/e2e/Services/Teams/TeamsBaseClient.php +++ b/tests/e2e/Services/Teams/TeamsBaseClient.php @@ -78,6 +78,19 @@ trait TeamsBaseClient /** * Test for FAILURE */ + + $response = $this->client->call(Client::METHOD_POST, '/teams/'.$teamUid.'/memberships', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'email' => $email, + 'name' => 'Friend User', + 'roles' => ['admin', 'editor'], + 'url' => 'http://localhost:5000/join-us#title' + ]); + + $this->assertEquals(409, $response['headers']['status-code']); + $response = $this->client->call(Client::METHOD_POST, '/teams/'.$teamUid.'/memberships', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], diff --git a/tests/e2e/Services/Teams/TeamsBaseServer.php b/tests/e2e/Services/Teams/TeamsBaseServer.php index 846c321b1b..4b977d4cf9 100644 --- a/tests/e2e/Services/Teams/TeamsBaseServer.php +++ b/tests/e2e/Services/Teams/TeamsBaseServer.php @@ -78,6 +78,19 @@ trait TeamsBaseServer /** * Test for FAILURE */ + + $response = $this->client->call(Client::METHOD_POST, '/teams/'.$teamUid.'/memberships', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'email' => $email, + 'name' => 'Friend User', + 'roles' => ['admin', 'editor'], + 'url' => 'http://localhost:5000/join-us#title' + ]); + + $this->assertEquals(409, $response['headers']['status-code']); + $response = $this->client->call(Client::METHOD_POST, '/teams/'.$teamUid.'/memberships', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], From 0a3c198e00e38e72ab29baa9e05742dcba6b5696 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sun, 9 May 2021 23:34:32 +0300 Subject: [PATCH 24/60] Removed boolean check on DB operation --- app/controllers/api/account.php | 75 +++++++++---------------------- app/controllers/api/functions.php | 24 ---------- app/controllers/api/storage.php | 8 ---- app/controllers/api/teams.php | 1 + app/workers/functions.php | 4 -- 5 files changed, 21 insertions(+), 91 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 7750980ab8..4abcd64f53 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -117,10 +117,6 @@ App::post('/v1/account') Authorization::setRole('user:'.$user->getId()); Authorization::setRole('role:'.Auth::USER_ROLE_MEMBER); - if (false === $user) { - throw new Exception('Failed saving user to DB', 500); - } - $audits ->setParam('userId', $user->getId()) ->setParam('event', 'account.create') @@ -203,10 +199,6 @@ App::post('/v1/account/sessions') $profile = $dbForInternal->updateDocument('users', $profile->getId(), $profile); - if (false === $profile) { - throw new Exception('Failed saving user to DB', 500); - } - $audits ->setParam('userId', $profile->getId()) ->setParam('event', 'account.sessions.create') @@ -537,10 +529,6 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') $user = $dbForInternal->updateDocument('users', $user->getId(), $user); - if (false === $user) { - throw new Exception('Failed saving user to DB', 500); - } - $audits ->setParam('userId', $user->getId()) ->setParam('event', 'account.sessions.create') @@ -629,33 +617,27 @@ App::post('/v1/account/sessions/anonymous') } Authorization::disable(); - try { - $userId = $dbForInternal->getId(); - $user = $dbForInternal->createDocument('users', new Document([ - '$id' => $userId, - '$read' => ['*'], - '$write' => ['user:'.$userId], - 'email' => null, - 'emailVerification' => false, - 'status' => Auth::USER_STATUS_UNACTIVATED, - 'password' => null, - 'passwordUpdate' => \time(), - 'registration' => \time(), - 'reset' => false, - 'name' => null, - 'prefs' => [], - 'sessions' => [], - 'tokens' => [], - 'memberships' => [], - ])); - } catch (Exception $th) { - throw new Exception('Failed saving user to DB', 500); - } - Authorization::reset(); - if (false === $user) { - throw new Exception('Failed saving user to DB', 500); - } + $userId = $dbForInternal->getId(); + $user = $dbForInternal->createDocument('users', new Document([ + '$id' => $userId, + '$read' => ['*'], + '$write' => ['user:'.$userId], + 'email' => null, + 'emailVerification' => false, + 'status' => Auth::USER_STATUS_UNACTIVATED, + 'password' => null, + 'passwordUpdate' => \time(), + 'registration' => \time(), + 'reset' => false, + 'name' => null, + 'prefs' => [], + 'sessions' => [], + 'tokens' => [], + 'memberships' => [], + ])); + + Authorization::reset(); // Create session token @@ -684,10 +666,6 @@ App::post('/v1/account/sessions/anonymous') $user = $dbForInternal->updateDocument('users', $user->getId(), $user); - if (false === $user) { - throw new Exception('Failed saving user to DB', 500); - } - $audits ->setParam('userId', $user->getId()) ->setParam('event', 'account.sessions.create') @@ -1035,10 +1013,6 @@ App::patch('/v1/account/email') ->setAttribute('emailVerification', false) // After this user needs to confirm mail again ); - if (false === $user) { - throw new Exception('Failed saving user to DB', 500); - } - $audits ->setParam('userId', $user->getId()) ->setParam('event', 'account.update.email') @@ -1109,11 +1083,6 @@ App::delete('/v1/account') $protocol = $request->getProtocol(); $user = $dbForInternal->updateDocument('users', $user->getId(), $user->setAttribute('status', Auth::USER_STATUS_BLOCKED)); - - if (false === $user) { - throw new Exception('Failed saving user to DB', 500); - } - //TODO delete all tokens or only current session? //TODO delete all user data according to GDPR. Make sure everything is backed up and backups are deleted later /* @@ -1634,10 +1603,6 @@ App::put('/v1/account/verification') $profile = $dbForInternal->updateDocument('users', $profile->getId(), $profile->setAttribute('emailVerification', true)); - if (false === $profile) { - throw new Exception('Failed saving user to DB', 500); - } - /** * We act like we're updating and validating * the verification token but actually we don't need it anymore. diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index 1b8c78c5ee..f882616263 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -66,10 +66,6 @@ App::post('/v1/functions') 'timeout' => $timeout, ])); - if (false === $function) { - throw new Exception('Failed saving function to DB', 500); - } - $response ->setStatusCode(Response::STATUS_CODE_CREATED) ->dynamic2($function, Response::MODEL_FUNCTION) @@ -299,10 +295,6 @@ App::put('/v1/functions/:functionId') 'timeout' => $timeout, ]))); - if (false === $function) { - throw new Exception('Failed saving function to DB', 500); - } - if ($next && $schedule !== $original) { ResqueScheduler::enqueueAt($next, 'v1-functions', 'FunctionsV1', [ 'projectId' => $project->getId(), @@ -366,10 +358,6 @@ App::patch('/v1/functions/:functionId/tag') ]); // Async task rescheduale } - if (false === $function) { - throw new Exception('Failed saving function to DB', 500); - } - $response->dynamic2($function, Response::MODEL_FUNCTION); }); @@ -489,10 +477,6 @@ App::post('/v1/functions/:functionId/tags') 'size' => $size, ])); - if (false === $tag) { - throw new Exception('Failed saving tag to DB', 500); - } - $usage ->setParam('storage', $tag->getAttribute('size', 0)) ; @@ -632,10 +616,6 @@ App::delete('/v1/functions/:functionId/tags/:tagId') $function = $dbForInternal->updateDocument('functions', $function->getId(), new Document(array_merge($function->getArrayCopy(), [ 'tag' => '', ]))); - - if (false === $function) { - throw new Exception('Failed saving function to DB', 500); - } } $usage @@ -715,10 +695,6 @@ App::post('/v1/functions/:functionId/executions') ])); Authorization::reset(); - - if (false === $execution) { - throw new Exception('Failed saving execution to DB', 500); - } $jwt = ''; // initialize if (!empty($user->getId())) { // If userId exists, generate a JWT for function diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index 5dda3f1fe7..7100ed3c8d 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -139,10 +139,6 @@ App::post('/v1/storage/files') 'openSSLIV' => \bin2hex($iv), ])); - if (false === $file) { - throw new Exception('Failed saving file to DB', 500); - } - $audits ->setParam('event', 'storage.files.create') ->setParam('resource', 'storage/files/'.$file->getId()) @@ -526,10 +522,6 @@ App::put('/v1/storage/files/:fileId') 'bucketId' => '', ]))); - if (false === $file) { - throw new Exception('Failed saving file to DB', 500); - } - $audits ->setParam('event', 'storage.files.update') ->setParam('resource', 'storage/files/'.$file->getId()) diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index fffb8b2ebb..32853e2bea 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -549,6 +549,7 @@ App::patch('/v1/teams/:teamId/memberships/:inviteId/status') Authorization::setRole('user:'.$userId); $user = $dbForInternal->updateDocument('users', $user->getId(), $user); + $membership = $dbForInternal->updateDocument('memberships', $membership->getId(), $membership); Authorization::disable(); diff --git a/app/workers/functions.php b/app/workers/functions.php index ad49ed6125..b77d147e90 100644 --- a/app/workers/functions.php +++ b/app/workers/functions.php @@ -458,10 +458,6 @@ class FunctionsV1 Authorization::reset(); - if (false === $function) { - throw new Exception('Failed saving execution to DB', 500); - } - $executionUpdate = new Event('v1-webhooks', 'WebhooksV1'); $executionUpdate From 22c1ac4abe8435d975725e512b0ed2dd3de02ec8 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Mon, 10 May 2021 00:20:57 +0300 Subject: [PATCH 25/60] Updated user status on deleted membership --- app/controllers/api/teams.php | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index 32853e2bea..8c5a769624 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -613,6 +613,12 @@ App::delete('/v1/teams/:teamId/memberships/:inviteId') throw new Exception('Team IDs don\'t match', 404); } + $user = $dbForInternal->getDocument('users', $membership->getAttribute('userId')); + + if ($user->isEmpty()) { + throw new Exception('User not found', 404); + } + $team = $dbForInternal->getDocument('teams', $teamId); if (empty($team->getId())) { @@ -623,6 +629,23 @@ App::delete('/v1/teams/:teamId/memberships/:inviteId') throw new Exception('Failed to remove membership from DB', 500); } + $memberships = $user->getAttribute('memberships', []); + + foreach ($memberships as $key => $child) { + /** @var Document $child */ + + if ($inviteId == $child->getId()) { + unset($memberships[$key]); + break; + } + } + + Authorization::disable(); + + $dbForInternal->updateDocument('users', $user->getId(), $user->setAttribute('memberships', $memberships)); + + Authorization::reset(); + if ($membership->getAttribute('confirm')) { // Count only confirmed members $team = $dbForInternal->updateDocument('teams', $team->getId(), $team->setAttribute('sum', $team->getAttribute('sum', 0) - 1)); } From e1ce368f665b9ce7a452710ac6d2a50c6b7409e3 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sat, 15 May 2021 23:03:56 +0300 Subject: [PATCH 26/60] Removed getArrayCopy --- app/controllers/api/functions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index f882616263..7faf6aa917 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -393,7 +393,7 @@ App::delete('/v1/functions/:functionId') $deletes ->setParam('type', DELETE_TYPE_DOCUMENT) - ->setParam('document', $function->getArrayCopy()) + ->setParam('document', $function) ; $response->noContent(); From 814ac68d4926edb335e5d53411c0974571d4b550 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sun, 16 May 2021 01:41:42 +0300 Subject: [PATCH 27/60] Work in progress - projects controller --- app/config/collections2.php | 224 +++++++++ app/controllers/api/projects.php | 786 +++++++++++++------------------ app/controllers/general.php | 17 +- app/init.php | 14 +- composer.json | 2 +- composer.lock | 44 +- 6 files changed, 589 insertions(+), 498 deletions(-) diff --git a/app/config/collections2.php b/app/config/collections2.php index 74d4a57ce3..bf942d5c40 100644 --- a/app/config/collections2.php +++ b/app/config/collections2.php @@ -7,6 +7,186 @@ $providers = Config::getParam('providers', []); $auth = Config::getParam('auth', []); $collections = [ + 'projects' => [ + '$collection' => Database::COLLECTIONS, + '$id' => 'projects', + 'name' => 'Projects', + 'attributes' => [ + [ + '$id' => 'name', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 128, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'description', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 256, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'teamId', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'logo', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'url', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 16384, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'usersAuthLimit', + 'type' => Database::VAR_INTEGER, + 'format' => '', + 'size' => 0, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'legalName', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 256, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'legalCountry', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 256, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'legalState', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 256, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'legalCity', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 256, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'legalAddress', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 256, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'legalTaxId', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 256, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'platforms', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 16384, + 'signed' => true, + 'required' => false, + 'array' => true, + 'filters' => ['json'], + ], + [ + '$id' => 'webhooks', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 16384, + 'signed' => true, + 'required' => false, + 'array' => true, + 'filters' => ['json'], + ], + [ + '$id' => 'keys', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 16384, + 'signed' => true, + 'required' => false, + 'array' => true, + 'filters' => ['json'], + ], + [ + '$id' => 'tasks', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 16384, + 'signed' => true, + 'required' => false, + 'array' => true, + 'filters' => ['json'], + ], + [ + '$id' => 'domains', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 16384, + 'signed' => true, + 'required' => false, + 'array' => true, + 'filters' => ['json'], + ], + ], + 'indexes' => [ + ], + ], + 'users' => [ '$collection' => Database::COLLECTIONS, '$id' => 'users', @@ -760,4 +940,48 @@ $collections = [ ], ]; +/* + * Add enabled OAuth2 providers to default data rules + */ +foreach ($providers as $index => $provider) { + if (!$provider['enabled']) { + continue; + } + + $collections['projects']['attributes'][] = [ + '$id' => 'usersOauth2'.\ucfirst($index).'Appid', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 16384, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ]; + + $collections['projects']['attributes'][] = [ + '$id' => 'usersOauth2'.\ucfirst($index).'Secret', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 16384, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ]; +} + +foreach ($auth as $index => $method) { + $collections['projects']['attributes'][] = [ + '$id' => $method['key'] ?? '', + 'type' => Database::VAR_BOOLEAN, + 'format' => '', + 'size' => 0, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ]; +} + return $collections; \ No newline at end of file diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index 0b5c1cb200..aeaa87be91 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -13,13 +13,12 @@ use Utopia\Config\Config; use Utopia\Domains\Domain; use Appwrite\Auth\Auth; use Appwrite\Task\Validator\Cron; -use Appwrite\Database\Database; -use Appwrite\Database\Document; -use Appwrite\Database\Validator\UID; use Appwrite\Network\Validator\CNAME; use Appwrite\Network\Validator\Domain as DomainValidator; use Appwrite\Utopia\Response; use Cron\CronExpression; +use Utopia\Database\Document; +use Utopia\Database\Validator\UID; App::post('/v1/projects') ->desc('Create Project') @@ -43,14 +42,12 @@ App::post('/v1/projects') ->param('legalAddress', '', new Text(256), 'Project legal Address. Max length: 256 chars.', true) ->param('legalTaxId', '', new Text(256), 'Project legal Tax ID. Max length: 256 chars.', true) ->inject('response') - ->inject('consoleDB') - ->inject('projectDB') + ->inject('dbForConsole') ->inject('dbForInternal') ->inject('dbForExternal') - ->action(function ($name, $teamId, $description, $logo, $url, $legalName, $legalCountry, $legalState, $legalCity, $legalAddress, $legalTaxId, $response, $consoleDB, $projectDB, $dbForInternal, $dbForExternal) { + ->action(function ($name, $teamId, $description, $logo, $url, $legalName, $legalCountry, $legalState, $legalCity, $legalAddress, $legalTaxId, $response, $dbForConsole, $dbForInternal, $dbForExternal) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $consoleDB */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Database $dbForConsole */ /** @var Utopia\Database\Database $dbForInternal */ /** @var Utopia\Database\Database $dbForExternal */ @@ -60,38 +57,32 @@ App::post('/v1/projects') throw new Exception('Team not found', 404); } - $project = $consoleDB->createDocument( - [ - '$collection' => Database::SYSTEM_COLLECTION_PROJECTS, - '$permissions' => [ - 'read' => ['team:'.$teamId], - 'write' => ['team:'.$teamId.'/owner', 'team:'.$teamId.'/developer'], - ], - 'name' => $name, - 'description' => $description, - 'logo' => $logo, - 'url' => $url, - 'legalName' => $legalName, - 'legalCountry' => $legalCountry, - 'legalState' => $legalState, - 'legalCity' => $legalCity, - 'legalAddress' => $legalAddress, - 'legalTaxId' => $legalTaxId, - 'teamId' => $team->getId(), - 'platforms' => [], - 'webhooks' => [], - 'keys' => [], - 'tasks' => [], - 'domains' => [], - ] - ); + $project = $dbForConsole->createDocument('projects', new Document([ + '$collection' => 'projects', + '$read' => ['team:'.$teamId], + '$write' => ['team:'.$teamId.'/owner', 'team:'.$teamId.'/developer'], + 'name' => $name, + 'description' => $description, + 'logo' => $logo, + 'url' => $url, + 'legalName' => $legalName, + 'legalCountry' => $legalCountry, + 'legalState' => $legalState, + 'legalCity' => $legalCity, + 'legalAddress' => $legalAddress, + 'legalTaxId' => $legalTaxId, + 'teamId' => $team->getId(), + 'platforms' => [], + 'webhooks' => [], + 'keys' => [], + 'tasks' => [], + 'domains' => [], + ])); if (false === $project) { throw new Exception('Failed saving project to DB', 500); } - $consoleDB->createNamespace($project->getId()); - $collections = Config::getParam('collections2', []); /** @var array $collections */ $dbForInternal->setNamespace('project_'.$project->getId().'_internal'); @@ -127,10 +118,8 @@ App::post('/v1/projects') } } - $response - ->setStatusCode(Response::STATUS_CODE_CREATED) - ->dynamic($project, Response::MODEL_PROJECT) - ; + $response->setStatusCode(Response::STATUS_CODE_CREATED); + $response->dynamic2($project, Response::MODEL_PROJECT); }); App::get('/v1/projects') @@ -148,23 +137,16 @@ App::get('/v1/projects') ->param('offset', 0, new Range(0, 2000), 'Results offset. The default value is 0. Use this param to manage pagination.', true) ->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true) ->inject('response') - ->inject('consoleDB') - ->action(function ($search, $limit, $offset, $orderType, $response, $consoleDB) { + ->inject('dbForConsole') + ->action(function ($search, $limit, $offset, $orderType, $response, $dbForConsole) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $consoleDB */ + /** @var Utopia\Database\Database $dbForConsole */ - $results = $consoleDB->getCollection([ - 'limit' => $limit, - 'offset' => $offset, - 'orderType' => $orderType, - 'search' => $search, - 'filters' => [ - '$collection='.Database::SYSTEM_COLLECTION_PROJECTS, - ], - ]); + $results = $dbForConsole->find('projects', [], $limit, $offset); + $sum = $dbForConsole->count('projects', [], APP_LIMIT_COUNT); - $response->dynamic(new Document([ - 'sum' => $consoleDB->getSum(), + $response->dynamic2(new Document([ + 'sum' => $sum, 'projects' => $results ]), Response::MODEL_PROJECT_LIST); }); @@ -181,18 +163,18 @@ App::get('/v1/projects/:projectId') ->label('sdk.response.model', Response::MODEL_PROJECT) ->param('projectId', '', new UID(), 'Project unique ID.') ->inject('response') - ->inject('consoleDB') - ->action(function ($projectId, $response, $consoleDB) { + ->inject('dbForConsole') + ->action(function ($projectId, $response, $dbForConsole) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $consoleDB */ + /** @var Utopia\Database\Database $dbForConsole */ - $project = $consoleDB->getDocument($projectId); + $project = $dbForConsole->getDocument('projects', $projectId); - if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { + if ($project->isEmpty()) { throw new Exception('Project not found', 404); } - $response->dynamic($project, Response::MODEL_PROJECT); + $response->dynamic2($project, Response::MODEL_PROJECT); }); App::get('/v1/projects/:projectId/usage') @@ -205,18 +187,18 @@ App::get('/v1/projects/:projectId/usage') ->param('projectId', '', new UID(), 'Project unique ID.') ->param('range', '30d', new WhiteList(['24h', '7d', '30d', '90d'], true), 'Date range.', true) ->inject('response') - ->inject('consoleDB') + ->inject('dbForConsole') ->inject('projectDB') ->inject('register') - ->action(function ($projectId, $range, $response, $consoleDB, $projectDB, $register) { + ->action(function ($projectId, $range, $response, $dbForConsole, $projectDB, $register) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $consoleDB */ + /** @var Utopia\Database\Database $dbForConsole */ /** @var Appwrite\Database\Database $projectDB */ /** @var Utopia\Registry\Registry $register */ - $project = $consoleDB->getDocument($projectId); + $project = $dbForConsole->getDocument('projects', $projectId); - if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { + if ($project->isEmpty()) { throw new Exception('Project not found', 404); } @@ -302,7 +284,7 @@ App::get('/v1/projects/:projectId/usage') 'limit' => 0, 'offset' => 0, 'filters' => [ - '$collection='.Database::SYSTEM_COLLECTION_USERS, + '$collection=users' ], ]); @@ -314,7 +296,7 @@ App::get('/v1/projects/:projectId/usage') 'limit' => 100, 'offset' => 0, 'filters' => [ - '$collection='.Database::SYSTEM_COLLECTION_COLLECTIONS, + '$collection=collections', ], ]); @@ -380,7 +362,7 @@ App::get('/v1/projects/:projectId/usage') [ 'attribute' => 'sizeOriginal', 'filters' => [ - '$collection='.Database::SYSTEM_COLLECTION_FILES, + '$collection=files', ], ] ) + @@ -388,7 +370,7 @@ App::get('/v1/projects/:projectId/usage') [ 'attribute' => 'size', 'filters' => [ - '$collection='.Database::SYSTEM_COLLECTION_TAGS, + '$collection=tags', ], ] ), @@ -418,35 +400,31 @@ App::patch('/v1/projects/:projectId') ->param('legalAddress', '', new Text(256), 'Project legal address. Max length: 256 chars.', true) ->param('legalTaxId', '', new Text(256), 'Project legal tax ID. Max length: 256 chars.', true) ->inject('response') - ->inject('consoleDB') - ->action(function ($projectId, $name, $description, $logo, $url, $legalName, $legalCountry, $legalState, $legalCity, $legalAddress, $legalTaxId, $response, $consoleDB) { + ->inject('dbForConsole') + ->action(function ($projectId, $name, $description, $logo, $url, $legalName, $legalCountry, $legalState, $legalCity, $legalAddress, $legalTaxId, $response, $dbForConsole) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $consoleDB */ + /** @var Utopia\Database\Database $dbForConsole */ - $project = $consoleDB->getDocument($projectId); + $project = $dbForConsole->getDocument('projects', $projectId); - if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { + if ($project->isEmpty()) { throw new Exception('Project not found', 404); } - $project = $consoleDB->updateDocument(\array_merge($project->getArrayCopy(), [ - 'name' => $name, - 'description' => $description, - 'logo' => $logo, - 'url' => $url, - 'legalName' => $legalName, - 'legalCountry' => $legalCountry, - 'legalState' => $legalState, - 'legalCity' => $legalCity, - 'legalAddress' => $legalAddress, - 'legalTaxId' => $legalTaxId, - ])); + $project = $dbForConsole->updateDocument('projects', $project->getId(), $project + ->setAttribute('name', $name) + ->setAttribute('description', $description) + ->setAttribute('logo', $logo) + ->setAttribute('url', $url) + ->setAttribute('legalName', $legalName) + ->setAttribute('legalCountry', $legalCountry) + ->setAttribute('legalState', $legalState) + ->setAttribute('legalCity', $legalCity) + ->setAttribute('legalAddress', $legalAddress) + ->setAttribute('legalTaxId', $legalTaxId) + ); - if (false === $project) { - throw new Exception('Failed saving project to DB', 500); - } - - $response->dynamic($project, Response::MODEL_PROJECT); + $response->dynamic2($project, Response::MODEL_PROJECT); }); App::patch('/v1/projects/:projectId/oauth2') @@ -464,27 +442,23 @@ App::patch('/v1/projects/:projectId/oauth2') ->param('appId', '', new Text(256), 'Provider app ID. Max length: 256 chars.', true) ->param('secret', '', new text(512), 'Provider secret key. Max length: 512 chars.', true) ->inject('response') - ->inject('consoleDB') - ->action(function ($projectId, $provider, $appId, $secret, $response, $consoleDB) { + ->inject('dbForConsole') + ->action(function ($projectId, $provider, $appId, $secret, $response, $dbForConsole) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $consoleDB */ + /** @var Utopia\Database\Database $dbForConsole */ - $project = $consoleDB->getDocument($projectId); + $project = $dbForConsole->getDocument('projects', $projectId); - if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { + if ($project->isEmpty()) { throw new Exception('Project not found', 404); } - $project = $consoleDB->updateDocument(\array_merge($project->getArrayCopy(), [ - 'usersOauth2'.\ucfirst($provider).'Appid' => $appId, - 'usersOauth2'.\ucfirst($provider).'Secret' => $secret, - ])); + $project = $dbForConsole->updateDocument('projects', $project->getId(), $project + ->setAttribute('usersOauth2'.\ucfirst($provider).'Appid', $appId) + ->setAttribute('usersOauth2'.\ucfirst($provider).'Secret', $secret) + ); - if (false === $project) { - throw new Exception('Failed saving project to DB', 500); - } - - $response->dynamic($project, Response::MODEL_PROJECT); + $response->dynamic2($project, Response::MODEL_PROJECT); }); App::patch('/v1/projects/:projectId/auth/limit') @@ -500,26 +474,22 @@ App::patch('/v1/projects/:projectId/auth/limit') ->param('projectId', '', new UID(), 'Project unique ID.') ->param('limit', false, new Integer(true), 'Set the max number of users allowed in this project. Use 0 for unlimited.') ->inject('response') - ->inject('consoleDB') - ->action(function ($projectId, $limit, $response, $consoleDB) { + ->inject('dbForConsole') + ->action(function ($projectId, $limit, $response, $dbForConsole) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $consoleDB */ + /** @var Utopia\Database\Database $dbForConsole */ - $project = $consoleDB->getDocument($projectId); + $project = $dbForConsole->getDocument('projects', $projectId); - if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { + if ($project->isEmpty()) { throw new Exception('Project not found', 404); } - if (false === $consoleDB->updateDocument( - \array_merge($project->getArrayCopy(), [ - 'usersAuthLimit' => $limit, - ])) - ) { - throw new Exception('Failed saving project to DB', 500); - }; + $dbForConsole->updateDocument('projects', $project->getId(), $project + ->setAttribute('usersAuthLimit', $limit) + ); - $response->dynamic($project, Response::MODEL_PROJECT); + $response->dynamic2($project, Response::MODEL_PROJECT); }); App::patch('/v1/projects/:projectId/auth/:method') @@ -536,29 +506,25 @@ App::patch('/v1/projects/:projectId/auth/:method') ->param('method', '', new WhiteList(\array_keys(Config::getParam('auth')), true), 'Auth Method. Possible values: '.implode(',', \array_keys(Config::getParam('auth'))), false) ->param('status', false, new Boolean(true), 'Set the status of this auth method.') ->inject('response') - ->inject('consoleDB') - ->action(function ($projectId, $method, $status, $response, $consoleDB) { + ->inject('dbForConsole') + ->action(function ($projectId, $method, $status, $response, $dbForConsole) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $consoleDB */ + /** @var Utopia\Database\Database $dbForConsole */ - $project = $consoleDB->getDocument($projectId); + $project = $dbForConsole->getDocument('projects', $projectId); $auth = Config::getParam('auth')[$method] ?? []; $authKey = $auth['key'] ?? ''; $status = ($status === '1' || $status === 'true' || $status === 1 || $status === true); - if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { + if ($project->isEmpty()) { throw new Exception('Project not found', 404); } - if (false === $consoleDB->updateDocument( - \array_merge($project->getArrayCopy(), [ - $authKey => $status, - ])) - ) { - throw new Exception('Failed saving project to DB', 500); - }; + $dbForConsole->updateDocument('projects', $project->getId(), $project + ->setAttribute($authKey, $status) + ); - $response->dynamic($project, Response::MODEL_PROJECT); + $response->dynamic2($project, Response::MODEL_PROJECT); }); App::delete('/v1/projects/:projectId') @@ -574,51 +540,34 @@ App::delete('/v1/projects/:projectId') ->param('password', '', new UID(), 'Your user password for confirmation. Must be between 6 to 32 chars.') ->inject('response') ->inject('user') - ->inject('consoleDB') + ->inject('dbForConsole') ->inject('deletes') - ->action(function ($projectId, $password, $response, $user, $consoleDB, $deletes) { + ->action(function ($projectId, $password, $response, $user, $dbForConsole, $deletes) { /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Document $user */ - /** @var Appwrite\Database\Database $consoleDB */ + /** @var Utopia\Database\Database $dbForConsole */ /** @var Appwrite\Event\Event $deletes */ if (!Auth::passwordVerify($password, $user->getAttribute('password'))) { // Double check user password throw new Exception('Invalid credentials', 401); } - $project = $consoleDB->getDocument($projectId); + $project = $dbForConsole->getDocument('projects', $projectId); - if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { + if ($project->isEmpty()) { throw new Exception('Project not found', 404); } $deletes ->setParam('type', DELETE_TYPE_DOCUMENT) - ->setParam('document', $project->getArrayCopy()) + ->setParam('document', $project) ; - - foreach (['keys', 'webhooks', 'tasks', 'platforms', 'domains'] as $key) { // Delete all children (keys, webhooks, tasks [stop tasks?], platforms) - $list = $project->getAttribute($key, []); - foreach ($list as $document) { - /** @var Document $document */ - if ($consoleDB->deleteDocument($document->getId())) { - if ($document->getCollection() == Database::SYSTEM_COLLECTION_DOMAINS) { - $deletes - ->setParam('type', DELETE_TYPE_CERTIFICATES) - ->setParam('document', $document) - ; - } - } else { - throw new Exception('Failed to remove project document ('.$key.')] from DB', 500); - } - } - } - if (!$consoleDB->deleteDocument($project->getAttribute('teamId', null))) { + if (!$dbForConsole->deleteDocument('teams', $project->getAttribute('teamId', null))) { throw new Exception('Failed to remove project team from DB', 500); } - if (!$consoleDB->deleteDocument($projectId)) { + if (!$dbForConsole->deleteDocument('projects', $projectId)) { throw new Exception('Failed to remove project from DB', 500); } @@ -645,25 +594,21 @@ App::post('/v1/projects/:projectId/webhooks') ->param('httpUser', '', new Text(256), 'Webhook HTTP user. Max length: 256 chars.', true) ->param('httpPass', '', new Text(256), 'Webhook HTTP password. Max length: 256 chars.', true) ->inject('response') - ->inject('consoleDB') - ->action(function ($projectId, $name, $events, $url, $security, $httpUser, $httpPass, $response, $consoleDB) { + ->inject('dbForConsole') + ->action(function ($projectId, $name, $events, $url, $security, $httpUser, $httpPass, $response, $dbForConsole) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $consoleDB */ + /** @var Utopia\Database\Database $dbForConsole */ - $project = $consoleDB->getDocument($projectId); + $project = $dbForConsole->getDocument('projects', $projectId); - if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { + if ($project->isEmpty()) { throw new Exception('Project not found', 404); } $security = ($security === '1' || $security === 'true' || $security === 1 || $security === true); - $webhook = $consoleDB->createDocument([ - '$collection' => Database::SYSTEM_COLLECTION_WEBHOOKS, - '$permissions' => [ - 'read' => ['team:'.$project->getAttribute('teamId', null)], - 'write' => ['team:'.$project->getAttribute('teamId', null).'/owner', 'team:'.$project->getAttribute('teamId', null).'/developer'], - ], + $webhook = new Document([ + '$id' => $dbForConsole->getId(), 'name' => $name, 'events' => $events, 'url' => $url, @@ -672,21 +617,13 @@ App::post('/v1/projects/:projectId/webhooks') 'httpPass' => $httpPass, ]); - if (false === $webhook) { - throw new Exception('Failed saving webhook to DB', 500); - } - - $project->setAttribute('webhooks', $webhook, Document::SET_TYPE_APPEND); - - $project = $consoleDB->updateDocument($project->getArrayCopy()); - - if (false === $project) { - throw new Exception('Failed saving project to DB', 500); - } + $project = $dbForConsole->updateDocument('projects', $project->getId(), $project + ->setAttribute('webhooks', $webhook, Document::SET_TYPE_APPEND) + ); $response ->setStatusCode(Response::STATUS_CODE_CREATED) - ->dynamic($webhook, Response::MODEL_WEBHOOK) + ->dynamic2($webhook, Response::MODEL_WEBHOOK) ; }); @@ -702,20 +639,20 @@ App::get('/v1/projects/:projectId/webhooks') ->label('sdk.response.model', Response::MODEL_WEBHOOK_LIST) ->param('projectId', '', new UID(), 'Project unique ID.') ->inject('response') - ->inject('consoleDB') - ->action(function ($projectId, $response, $consoleDB) { + ->inject('dbForConsole') + ->action(function ($projectId, $response, $dbForConsole) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $consoleDB */ + /** @var Utopia\Database\Database $dbForConsole */ - $project = $consoleDB->getDocument($projectId); + $project = $dbForConsole->getDocument('projects', $projectId); - if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { + if ($project->isEmpty()) { throw new Exception('Project not found', 404); } $webhooks = $project->getAttribute('webhooks', []); - $response->dynamic(new Document([ + $response->dynamic2(new Document([ 'sum' => count($webhooks), 'webhooks' => $webhooks ]), Response::MODEL_WEBHOOK_LIST); @@ -734,24 +671,24 @@ App::get('/v1/projects/:projectId/webhooks/:webhookId') ->param('projectId', null, new UID(), 'Project unique ID.') ->param('webhookId', null, new UID(), 'Webhook unique ID.') ->inject('response') - ->inject('consoleDB') - ->action(function ($projectId, $webhookId, $response, $consoleDB) { + ->inject('dbForConsole') + ->action(function ($projectId, $webhookId, $response, $dbForConsole) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $consoleDB */ + /** @var Utopia\Database\Database $dbForConsole */ - $project = $consoleDB->getDocument($projectId); + $project = $dbForConsole->getDocument('projects', $projectId); - if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { + if ($project->isEmpty()) { throw new Exception('Project not found', 404); } - $webhook = $project->search('$id', $webhookId, $project->getAttribute('webhooks', [])); + $webhook = $project->find('$id', $webhookId, 'webhooks'); if (empty($webhook) || !$webhook instanceof Document) { throw new Exception('Webhook not found', 404); } - $response->dynamic($webhook, Response::MODEL_WEBHOOK); + $response->dynamic2($webhook, Response::MODEL_WEBHOOK); }); App::put('/v1/projects/:projectId/webhooks/:webhookId') @@ -773,39 +710,37 @@ App::put('/v1/projects/:projectId/webhooks/:webhookId') ->param('httpUser', '', new Text(256), 'Webhook HTTP user. Max length: 256 chars.', true) ->param('httpPass', '', new Text(256), 'Webhook HTTP password. Max length: 256 chars.', true) ->inject('response') - ->inject('consoleDB') - ->action(function ($projectId, $webhookId, $name, $events, $url, $security, $httpUser, $httpPass, $response, $consoleDB) { + ->inject('dbForConsole') + ->action(function ($projectId, $webhookId, $name, $events, $url, $security, $httpUser, $httpPass, $response, $dbForConsole) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $consoleDB */ + /** @var Utopia\Database\Database $dbForConsole */ - $project = $consoleDB->getDocument($projectId); + $project = $dbForConsole->getDocument('projects', $projectId); - if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { + if ($project->isEmpty()) { throw new Exception('Project not found', 404); } $security = ($security === '1' || $security === 'true' || $security === 1 || $security === true); - $webhook = $project->search('$id', $webhookId, $project->getAttribute('webhooks', [])); + $webhook = $project->find('$id', $webhookId, 'webhooks'); if (empty($webhook) || !$webhook instanceof Document) { throw new Exception('Webhook not found', 404); } - $webhook + $project->findAndReplace('$id', $webhook->getId(), $webhook ->setAttribute('name', $name) ->setAttribute('events', $events) ->setAttribute('url', $url) ->setAttribute('security', $security) ->setAttribute('httpUser', $httpUser) ->setAttribute('httpPass', $httpPass) - ; + , 'webhooks'); - if (false === $consoleDB->updateDocument($webhook->getArrayCopy())) { - throw new Exception('Failed saving webhook to DB', 500); - } - - $response->dynamic($webhook, Response::MODEL_WEBHOOK); + $dbForConsole->updateDocument('projects', $project->getId(), $project); + + $response->dynamic2($webhook, Response::MODEL_WEBHOOK); }); App::delete('/v1/projects/:projectId/webhooks/:webhookId') @@ -820,26 +755,22 @@ App::delete('/v1/projects/:projectId/webhooks/:webhookId') ->param('projectId', null, new UID(), 'Project unique ID.') ->param('webhookId', null, new UID(), 'Webhook unique ID.') ->inject('response') - ->inject('consoleDB') - ->action(function ($projectId, $webhookId, $response, $consoleDB) { + ->inject('dbForConsole') + ->action(function ($projectId, $webhookId, $response, $dbForConsole) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $consoleDB */ + /** @var Utopia\Database\Database $dbForConsole */ - $project = $consoleDB->getDocument($projectId); + $project = $dbForConsole->getDocument('projects', $projectId); - if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { + if ($project->isEmpty()) { throw new Exception('Project not found', 404); } - $webhook = $project->search('$id', $webhookId, $project->getAttribute('webhooks', [])); - - if (empty($webhook) || !$webhook instanceof Document) { + if (!$project->findAndRemove('$id', $webhookId, 'webhooks')) { throw new Exception('Webhook not found', 404); } - if (!$consoleDB->deleteDocument($webhook->getId())) { - throw new Exception('Failed to remove webhook from DB', 500); - } + $dbForConsole->updateDocument('projects', $project->getId(), $project); $response->noContent(); }); @@ -860,43 +791,31 @@ App::post('/v1/projects/:projectId/keys') ->param('name', null, new Text(128), 'Key name. Max length: 128 chars.') ->param('scopes', null, new ArrayList(new WhiteList(array_keys(Config::getParam('scopes')), true)), 'Key scopes list.') ->inject('response') - ->inject('consoleDB') - ->action(function ($projectId, $name, $scopes, $response, $consoleDB) { + ->inject('dbForConsole') + ->action(function ($projectId, $name, $scopes, $response, $dbForConsole) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $consoleDB */ + /** @var Utopia\Database\Database $dbForConsole */ - $project = $consoleDB->getDocument($projectId); + $project = $dbForConsole->getDocument('projects', $projectId); - if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { + if ($project->isEmpty()) { throw new Exception('Project not found', 404); } - $key = $consoleDB->createDocument([ - '$collection' => Database::SYSTEM_COLLECTION_KEYS, - '$permissions' => [ - 'read' => ['team:'.$project->getAttribute('teamId', null)], - 'write' => ['team:'.$project->getAttribute('teamId', null).'/owner', 'team:'.$project->getAttribute('teamId', null).'/developer'], - ], + $key = new Document([ + '$id' => $dbForConsole->getId(), 'name' => $name, 'scopes' => $scopes, 'secret' => \bin2hex(\random_bytes(128)), ]); - if (false === $key) { - throw new Exception('Failed saving key to DB', 500); - } - - $project->setAttribute('keys', $key, Document::SET_TYPE_APPEND); - - $project = $consoleDB->updateDocument($project->getArrayCopy()); - - if (false === $project) { - throw new Exception('Failed saving project to DB', 500); - } + $project = $dbForConsole->updateDocument('projects', $project->getId(), $project + ->setAttribute('keys', $key, Document::SET_TYPE_APPEND) + ); $response ->setStatusCode(Response::STATUS_CODE_CREATED) - ->dynamic($key, Response::MODEL_KEY) + ->dynamic2($key, Response::MODEL_KEY) ; }); @@ -912,20 +831,20 @@ App::get('/v1/projects/:projectId/keys') ->label('sdk.response.model', Response::MODEL_KEY_LIST) ->param('projectId', null, new UID(), 'Project unique ID.') ->inject('response') - ->inject('consoleDB') - ->action(function ($projectId, $response, $consoleDB) { + ->inject('dbForConsole') + ->action(function ($projectId, $response, $dbForConsole) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $consoleDB */ + /** @var Utopia\Database\Database $dbForConsole */ - $project = $consoleDB->getDocument($projectId); + $project = $dbForConsole->getDocument('projects', $projectId); - if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { + if ($project->isEmpty()) { throw new Exception('Project not found', 404); } $keys = $project->getAttribute('keys', []); - $response->dynamic(new Document([ + $response->dynamic2(new Document([ 'sum' => count($keys), 'keys' => $keys ]), Response::MODEL_KEY_LIST); @@ -944,21 +863,21 @@ App::get('/v1/projects/:projectId/keys/:keyId') ->param('projectId', null, new UID(), 'Project unique ID.') ->param('keyId', null, new UID(), 'Key unique ID.') ->inject('response') - ->inject('consoleDB') - ->action(function ($projectId, $keyId, $response, $consoleDB) { - $project = $consoleDB->getDocument($projectId); + ->inject('dbForConsole') + ->action(function ($projectId, $keyId, $response, $dbForConsole) { + $project = $dbForConsole->getDocument('projects', $projectId); - if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { + if ($project->isEmpty()) { throw new Exception('Project not found', 404); } - $key = $project->search('$id', $keyId, $project->getAttribute('keys', [])); + $key = $project->find('$id', $keyId, 'keys'); if (empty($key) || !$key instanceof Document) { throw new Exception('Key not found', 404); } - $response->dynamic($key, Response::MODEL_KEY); + $response->dynamic2($key, Response::MODEL_KEY); }); App::put('/v1/projects/:projectId/keys/:keyId') @@ -976,33 +895,31 @@ App::put('/v1/projects/:projectId/keys/:keyId') ->param('name', null, new Text(128), 'Key name. Max length: 128 chars.') ->param('scopes', null, new ArrayList(new WhiteList(array_keys(Config::getParam('scopes')), true)), 'Key scopes list') ->inject('response') - ->inject('consoleDB') - ->action(function ($projectId, $keyId, $name, $scopes, $response, $consoleDB) { + ->inject('dbForConsole') + ->action(function ($projectId, $keyId, $name, $scopes, $response, $dbForConsole) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $consoleDB */ + /** @var Utopia\Database\Database $dbForConsole */ - $project = $consoleDB->getDocument($projectId); + $project = $dbForConsole->getDocument('projects', $projectId); - if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { + if ($project->isEmpty()) { throw new Exception('Project not found', 404); } - $key = $project->search('$id', $keyId, $project->getAttribute('keys', [])); + $key = $project->find('$id', $keyId, 'keys'); if (empty($key) || !$key instanceof Document) { throw new Exception('Key not found', 404); } - $key + $project->findAndReplace('$id', $key->getId(), $key ->setAttribute('name', $name) ->setAttribute('scopes', $scopes) - ; + , 'keys'); - if (false === $consoleDB->updateDocument($key->getArrayCopy())) { - throw new Exception('Failed saving key to DB', 500); - } - - $response->dynamic($key, Response::MODEL_KEY); + $dbForConsole->updateDocument('projects', $project->getId(), $project); + + $response->dynamic2($key, Response::MODEL_KEY); }); App::delete('/v1/projects/:projectId/keys/:keyId') @@ -1017,26 +934,22 @@ App::delete('/v1/projects/:projectId/keys/:keyId') ->param('projectId', null, new UID(), 'Project unique ID.') ->param('keyId', null, new UID(), 'Key unique ID.') ->inject('response') - ->inject('consoleDB') - ->action(function ($projectId, $keyId, $response, $consoleDB) { + ->inject('dbForConsole') + ->action(function ($projectId, $keyId, $response, $dbForConsole) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $consoleDB */ + /** @var Utopia\Database\Database $dbForConsole */ - $project = $consoleDB->getDocument($projectId); + $project = $dbForConsole->getDocument('projects', $projectId); - if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { + if ($project->isEmpty()) { throw new Exception('Project not found', 404); } - $key = $project->search('$id', $keyId, $project->getAttribute('keys', [])); - - if (empty($key) || !$key instanceof Document) { + if (!$project->findAndRemove('$id', $keyId, 'keys')) { throw new Exception('Key not found', 404); } - if (!$consoleDB->deleteDocument($key->getId())) { - throw new Exception('Failed to remove key from DB', 500); - } + $dbForConsole->updateDocument('projects', $project->getId(), $project); $response->noContent(); }); @@ -1064,28 +977,23 @@ App::post('/v1/projects/:projectId/tasks') ->param('httpUser', '', new Text(256), 'Task HTTP user. Max length: 256 chars.', true) ->param('httpPass', '', new Text(256), 'Task HTTP password. Max length: 256 chars.', true) ->inject('response') - ->inject('consoleDB') - ->action(function ($projectId, $name, $status, $schedule, $security, $httpMethod, $httpUrl, $httpHeaders, $httpUser, $httpPass, $response, $consoleDB) { + ->inject('dbForConsole') + ->action(function ($projectId, $name, $status, $schedule, $security, $httpMethod, $httpUrl, $httpHeaders, $httpUser, $httpPass, $response, $dbForConsole) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $consoleDB */ + /** @var Utopia\Database\Database $dbForConsole */ - $project = $consoleDB->getDocument($projectId); + $project = $dbForConsole->getDocument('projects', $projectId); - if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { + if ($project->isEmpty()) { throw new Exception('Project not found', 404); } $cron = new CronExpression($schedule); $next = ($status == 'play') ? $cron->getNextRunDate()->format('U') : null; - $security = ($security === '1' || $security === 'true' || $security === 1 || $security === true); - $task = $consoleDB->createDocument([ - '$collection' => Database::SYSTEM_COLLECTION_TASKS, - '$permissions' => [ - 'read' => ['team:'.$project->getAttribute('teamId', null)], - 'write' => ['team:'.$project->getAttribute('teamId', null).'/owner', 'team:'.$project->getAttribute('teamId', null).'/developer'], - ], + $task = new Document([ + '$id' => $dbForConsole->getId(), 'name' => $name, 'status' => $status, 'schedule' => $schedule, @@ -1102,17 +1010,9 @@ App::post('/v1/projects/:projectId/tasks') 'failures' => 0, ]); - if (false === $task) { - throw new Exception('Failed saving tasks to DB', 500); - } - - $project->setAttribute('tasks', $task, Document::SET_TYPE_APPEND); - - $project = $consoleDB->updateDocument($project->getArrayCopy()); - - if (false === $project) { - throw new Exception('Failed saving project to DB', 500); - } + $project = $dbForConsole->updateDocument('projects', $project->getId(), $project + ->setAttribute('tasks', $task, Document::SET_TYPE_APPEND) + ); if ($next) { ResqueScheduler::enqueueAt($next, 'v1-tasks', 'TasksV1', $task->getArrayCopy()); @@ -1120,7 +1020,7 @@ App::post('/v1/projects/:projectId/tasks') $response ->setStatusCode(Response::STATUS_CODE_CREATED) - ->dynamic($task, Response::MODEL_TASK) + ->dynamic2($task, Response::MODEL_TASK) ; }); @@ -1136,20 +1036,20 @@ App::get('/v1/projects/:projectId/tasks') ->label('sdk.response.model', Response::MODEL_TASK_LIST) ->param('projectId', '', new UID(), 'Project unique ID.') ->inject('response') - ->inject('consoleDB') - ->action(function ($projectId, $response, $consoleDB) { + ->inject('dbForConsole') + ->action(function ($projectId, $response, $dbForConsole) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $consoleDB */ + /** @var Utopia\Database\Database $dbForConsole */ - $project = $consoleDB->getDocument($projectId); + $project = $dbForConsole->getDocument('projects', $projectId); - if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { + if ($project->isEmpty()) { throw new Exception('Project not found', 404); } $tasks = $project->getAttribute('tasks', []); - $response->dynamic(new Document([ + $response->dynamic2(new Document([ 'sum' => count($tasks), 'tasks' => $tasks ]), Response::MODEL_TASK_LIST); @@ -1169,24 +1069,24 @@ App::get('/v1/projects/:projectId/tasks/:taskId') ->param('projectId', null, new UID(), 'Project unique ID.') ->param('taskId', null, new UID(), 'Task unique ID.') ->inject('response') - ->inject('consoleDB') - ->action(function ($projectId, $taskId, $response, $consoleDB) { + ->inject('dbForConsole') + ->action(function ($projectId, $taskId, $response, $dbForConsole) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $consoleDB */ + /** @var Utopia\Database\Database $dbForConsole */ - $project = $consoleDB->getDocument($projectId); + $project = $dbForConsole->getDocument('projects', $projectId); - if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { + if ($project->isEmpty()) { throw new Exception('Project not found', 404); } - $task = $project->search('$id', $taskId, $project->getAttribute('tasks', [])); + $task = $project->find('$id', $taskId, 'tasks'); if (empty($task) || !$task instanceof Document) { throw new Exception('Task not found', 404); } - $response->dynamic($task, Response::MODEL_TASK); + $response->dynamic2($task, Response::MODEL_TASK); }); App::put('/v1/projects/:projectId/tasks/:taskId') @@ -1211,18 +1111,18 @@ App::put('/v1/projects/:projectId/tasks/:taskId') ->param('httpUser', '', new Text(256), 'Task HTTP user. Max length: 256 chars.', true) ->param('httpPass', '', new Text(256), 'Task HTTP password. Max length: 256 chars.', true) ->inject('response') - ->inject('consoleDB') - ->action(function ($projectId, $taskId, $name, $status, $schedule, $security, $httpMethod, $httpUrl, $httpHeaders, $httpUser, $httpPass, $response, $consoleDB) { + ->inject('dbForConsole') + ->action(function ($projectId, $taskId, $name, $status, $schedule, $security, $httpMethod, $httpUrl, $httpHeaders, $httpUser, $httpPass, $response, $dbForConsole) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $consoleDB */ + /** @var Utopia\Database\Database $dbForConsole */ - $project = $consoleDB->getDocument($projectId); + $project = $dbForConsole->getDocument('projects', $projectId); - if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { + if ($project->isEmpty()) { throw new Exception('Project not found', 404); } - $task = $project->search('$id', $taskId, $project->getAttribute('tasks', [])); + $task = $project->find('$id', $taskId, 'tasks'); if (empty($task) || !$task instanceof Document) { throw new Exception('Task not found', 404); @@ -1230,10 +1130,9 @@ App::put('/v1/projects/:projectId/tasks/:taskId') $cron = new CronExpression($schedule); $next = ($status == 'play') ? $cron->getNextRunDate()->format('U') : null; - $security = ($security === '1' || $security === 'true' || $security === 1 || $security === true); - $task + $project->findAndReplace('$id', $task->getId(), $task ->setAttribute('name', $name) ->setAttribute('status', $status) ->setAttribute('schedule', $schedule) @@ -1245,17 +1144,15 @@ App::put('/v1/projects/:projectId/tasks/:taskId') ->setAttribute('httpHeaders', $httpHeaders) ->setAttribute('httpUser', $httpUser) ->setAttribute('httpPass', $httpPass) - ; + , 'tasks'); - if (false === $consoleDB->updateDocument($task->getArrayCopy())) { - throw new Exception('Failed saving tasks to DB', 500); - } + $dbForConsole->updateDocument('projects', $project->getId(), $project); if ($next) { ResqueScheduler::enqueueAt($next, 'v1-tasks', 'TasksV1', $task->getArrayCopy()); } - $response->dynamic($task, Response::MODEL_TASK); + $response->dynamic2($task, Response::MODEL_TASK); }); App::delete('/v1/projects/:projectId/tasks/:taskId') @@ -1270,26 +1167,22 @@ App::delete('/v1/projects/:projectId/tasks/:taskId') ->param('projectId', null, new UID(), 'Project unique ID.') ->param('taskId', null, new UID(), 'Task unique ID.') ->inject('response') - ->inject('consoleDB') - ->action(function ($projectId, $taskId, $response, $consoleDB) { + ->inject('dbForConsole') + ->action(function ($projectId, $taskId, $response, $dbForConsole) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $consoleDB */ + /** @var Utopia\Database\Database $dbForConsole */ - $project = $consoleDB->getDocument($projectId); + $project = $dbForConsole->getDocument('projects', $projectId); - if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { + if ($project->isEmpty()) { throw new Exception('Project not found', 404); } - $task = $project->search('$id', $taskId, $project->getAttribute('tasks', [])); - - if (empty($task) || !$task instanceof Document) { + if (!$project->findAndRemove('$id', $taskId, 'tasks')) { throw new Exception('Task not found', 404); } - if (!$consoleDB->deleteDocument($task->getId())) { - throw new Exception('Failed to remove tasks from DB', 500); - } + $dbForConsole->updateDocument('projects', $project->getId(), $project); $response->noContent(); }); @@ -1313,23 +1206,19 @@ App::post('/v1/projects/:projectId/platforms') ->param('store', '', new Text(256), 'App store or Google Play store ID. Max length: 256 chars.', true) ->param('hostname', '', new Text(256), 'Platform client hostname. Max length: 256 chars.', true) ->inject('response') - ->inject('consoleDB') - ->action(function ($projectId, $type, $name, $key, $store, $hostname, $response, $consoleDB) { + ->inject('dbForConsole') + ->action(function ($projectId, $type, $name, $key, $store, $hostname, $response, $dbForConsole) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $consoleDB */ + /** @var Utopia\Database\Database $dbForConsole */ - $project = $consoleDB->getDocument($projectId); + $project = $dbForConsole->getDocument('projects', $projectId); - if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { + if ($project->isEmpty()) { throw new Exception('Project not found', 404); } - $platform = $consoleDB->createDocument([ - '$collection' => Database::SYSTEM_COLLECTION_PLATFORMS, - '$permissions' => [ - 'read' => ['team:'.$project->getAttribute('teamId', null)], - 'write' => ['team:'.$project->getAttribute('teamId', null).'/owner', 'team:'.$project->getAttribute('teamId', null).'/developer'], - ], + $platform = new Document([ + '$id' => $dbForConsole->getId(), 'type' => $type, 'name' => $name, 'key' => $key, @@ -1339,21 +1228,13 @@ App::post('/v1/projects/:projectId/platforms') 'dateUpdated' => \time(), ]); - if (false === $platform) { - throw new Exception('Failed saving platform to DB', 500); - } - - $project->setAttribute('platforms', $platform, Document::SET_TYPE_APPEND); - - $project = $consoleDB->updateDocument($project->getArrayCopy()); - - if (false === $project) { - throw new Exception('Failed saving project to DB', 500); - } + $project = $dbForConsole->updateDocument('projects', $project->getId(), $project + ->setAttribute('platforms', $platform, Document::SET_TYPE_APPEND) + ); $response ->setStatusCode(Response::STATUS_CODE_CREATED) - ->dynamic($platform, Response::MODEL_PLATFORM) + ->dynamic2($platform, Response::MODEL_PLATFORM) ; }); @@ -1369,20 +1250,20 @@ App::get('/v1/projects/:projectId/platforms') ->label('sdk.response.model', Response::MODEL_PLATFORM_LIST) ->param('projectId', '', new UID(), 'Project unique ID.') ->inject('response') - ->inject('consoleDB') - ->action(function ($projectId, $response, $consoleDB) { + ->inject('dbForConsole') + ->action(function ($projectId, $response, $dbForConsole) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $consoleDB */ + /** @var Utopia\Database\Database $dbForConsole */ - $project = $consoleDB->getDocument($projectId); + $project = $dbForConsole->getDocument('projects', $projectId); - if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { + if ($project->isEmpty()) { throw new Exception('Project not found', 404); } $platforms = $project->getAttribute('platforms', []); - $response->dynamic(new Document([ + $response->dynamic2(new Document([ 'sum' => count($platforms), 'platforms' => $platforms ]), Response::MODEL_PLATFORM_LIST); @@ -1401,24 +1282,24 @@ App::get('/v1/projects/:projectId/platforms/:platformId') ->param('projectId', null, new UID(), 'Project unique ID.') ->param('platformId', null, new UID(), 'Platform unique ID.') ->inject('response') - ->inject('consoleDB') - ->action(function ($projectId, $platformId, $response, $consoleDB) { + ->inject('dbForConsole') + ->action(function ($projectId, $platformId, $response, $dbForConsole) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $consoleDB */ + /** @var Utopia\Database\Database $dbForConsole */ - $project = $consoleDB->getDocument($projectId); + $project = $dbForConsole->getDocument('projects', $projectId); - if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { + if ($project->isEmpty()) { throw new Exception('Project not found', 404); } - $platform = $project->search('$id', $platformId, $project->getAttribute('platforms', [])); + $platform = $project->find('$id', $platformId, 'platforms'); if (empty($platform) || !$platform instanceof Document) { throw new Exception('Platform not found', 404); } - $response->dynamic($platform, Response::MODEL_PLATFORM); + $response->dynamic2($platform, Response::MODEL_PLATFORM); }); App::put('/v1/projects/:projectId/platforms/:platformId') @@ -1438,18 +1319,18 @@ App::put('/v1/projects/:projectId/platforms/:platformId') ->param('store', '', new Text(256), 'App store or Google Play store ID. Max length: 256 chars.', true) ->param('hostname', '', new Text(256), 'Platform client URL. Max length: 256 chars.', true) ->inject('response') - ->inject('consoleDB') - ->action(function ($projectId, $platformId, $name, $key, $store, $hostname, $response, $consoleDB) { + ->inject('dbForConsole') + ->action(function ($projectId, $platformId, $name, $key, $store, $hostname, $response, $dbForConsole) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $consoleDB */ + /** @var Utopia\Database\Database $dbForConsole */ - $project = $consoleDB->getDocument($projectId); + $project = $dbForConsole->getDocument('projects', $projectId); - if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { + if ($project->isEmpty()) { throw new Exception('Project not found', 404); } - $platform = $project->search('$id', $platformId, $project->getAttribute('platforms', [])); + $platform = $project->find('$id', $platformId, 'platforms'); if (empty($platform) || !$platform instanceof Document) { throw new Exception('Platform not found', 404); @@ -1463,11 +1344,17 @@ App::put('/v1/projects/:projectId/platforms/:platformId') ->setAttribute('hostname', $hostname) ; - if (false === $consoleDB->updateDocument($platform->getArrayCopy())) { - throw new Exception('Failed saving platform to DB', 500); - } + $project->findAndReplace('$id', $platform->getId(), $platform + ->setAttribute('name', $name) + ->setAttribute('dateUpdated', \time()) + ->setAttribute('key', $key) + ->setAttribute('store', $store) + ->setAttribute('hostname', $hostname) + , 'platforms'); - $response->dynamic($platform, Response::MODEL_PLATFORM); + $dbForConsole->updateDocument('projects', $project->getId(), $project); + + $response->dynamic2($platform, Response::MODEL_PLATFORM); }); App::delete('/v1/projects/:projectId/platforms/:platformId') @@ -1482,26 +1369,22 @@ App::delete('/v1/projects/:projectId/platforms/:platformId') ->param('projectId', null, new UID(), 'Project unique ID.') ->param('platformId', null, new UID(), 'Platform unique ID.') ->inject('response') - ->inject('consoleDB') - ->action(function ($projectId, $platformId, $response, $consoleDB) { + ->inject('dbForConsole') + ->action(function ($projectId, $platformId, $response, $dbForConsole) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $consoleDB */ + /** @var Utopia\Database\Database $dbForConsole */ - $project = $consoleDB->getDocument($projectId); + $project = $dbForConsole->getDocument('projects', $projectId); - if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { + if ($project->isEmpty()) { throw new Exception('Project not found', 404); } - $platform = $project->search('$id', $platformId, $project->getAttribute('platforms', [])); - - if (empty($platform) || !$platform instanceof Document) { + if (!$project->findAndRemove('$id', $platformId, 'platforms')) { throw new Exception('Platform not found', 404); } - if (!$consoleDB->deleteDocument($platform->getId())) { - throw new Exception('Failed to remove platform from DB', 500); - } + $dbForConsole->updateDocument('projects', $project->getId(), $project); $response->noContent(); }); @@ -1521,20 +1404,20 @@ App::post('/v1/projects/:projectId/domains') ->param('projectId', null, new UID(), 'Project unique ID.') ->param('domain', null, new DomainValidator(), 'Domain name.') ->inject('response') - ->inject('consoleDB') - ->action(function ($projectId, $domain, $response, $consoleDB) { + ->inject('dbForConsole') + ->action(function ($projectId, $domain, $response, $dbForConsole) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $consoleDB */ + /** @var Utopia\Database\Database $dbForConsole */ - $project = $consoleDB->getDocument($projectId); + $project = $dbForConsole->getDocument('projects', $projectId); - if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { + if ($project->isEmpty()) { throw new Exception('Project not found', 404); } - $document = $project->search('domain', $domain, $project->getAttribute('domains', [])); + $document = $project->find('domain', $domain, 'domains'); - if (!empty($document)) { + if ($document) { throw new Exception('Domain already exists', 409); } @@ -1546,12 +1429,8 @@ App::post('/v1/projects/:projectId/domains') $domain = new Domain($domain); - $domain = $consoleDB->createDocument([ - '$collection' => Database::SYSTEM_COLLECTION_DOMAINS, - '$permissions' => [ - 'read' => ['team:'.$project->getAttribute('teamId', null)], - 'write' => ['team:'.$project->getAttribute('teamId', null).'/owner', 'team:'.$project->getAttribute('teamId', null).'/developer'], - ], + $domain = new Document([ + '$id' => $dbForConsole->getId(), 'updated' => \time(), 'domain' => $domain->get(), 'tld' => $domain->getSuffix(), @@ -1560,21 +1439,13 @@ App::post('/v1/projects/:projectId/domains') 'certificateId' => null, ]); - if (false === $domain) { - throw new Exception('Failed saving domain to DB', 500); - } - - $project->setAttribute('domains', $domain, Document::SET_TYPE_APPEND); - - $project = $consoleDB->updateDocument($project->getArrayCopy()); - - if (false === $project) { - throw new Exception('Failed saving project to DB', 500); - } + $project = $dbForConsole->updateDocument('projects', $project->getId(), $project + ->setAttribute('domains', $domain, Document::SET_TYPE_APPEND) + ); $response ->setStatusCode(Response::STATUS_CODE_CREATED) - ->dynamic($domain, Response::MODEL_DOMAIN) + ->dynamic2($domain, Response::MODEL_DOMAIN) ; }); @@ -1590,20 +1461,20 @@ App::get('/v1/projects/:projectId/domains') ->label('sdk.response.model', Response::MODEL_DOMAIN_LIST) ->param('projectId', '', new UID(), 'Project unique ID.') ->inject('response') - ->inject('consoleDB') - ->action(function ($projectId, $response, $consoleDB) { + ->inject('dbForConsole') + ->action(function ($projectId, $response, $dbForConsole) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $consoleDB */ + /** @var Utopia\Database\Database $dbForConsole */ - $project = $consoleDB->getDocument($projectId); + $project = $dbForConsole->getDocument('projects', $projectId); - if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { + if ($project->isEmpty()) { throw new Exception('Project not found', 404); } $domains = $project->getAttribute('domains', []); - $response->dynamic(new Document([ + $response->dynamic2(new Document([ 'sum' => count($domains), 'domains' => $domains ]), Response::MODEL_DOMAIN_LIST); @@ -1622,24 +1493,24 @@ App::get('/v1/projects/:projectId/domains/:domainId') ->param('projectId', null, new UID(), 'Project unique ID.') ->param('domainId', null, new UID(), 'Domain unique ID.') ->inject('response') - ->inject('consoleDB') - ->action(function ($projectId, $domainId, $response, $consoleDB) { + ->inject('dbForConsole') + ->action(function ($projectId, $domainId, $response, $dbForConsole) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $consoleDB */ + /** @var Utopia\Database\Database $dbForConsole */ - $project = $consoleDB->getDocument($projectId); + $project = $dbForConsole->getDocument('projects', $projectId); - if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { + if ($project->isEmpty()) { throw new Exception('Project not found', 404); } - $domain = $project->search('$id', $domainId, $project->getAttribute('domains', [])); + $domain = $project->find('$id', $domainId, 'domains'); if (empty($domain) || !$domain instanceof Document) { throw new Exception('Domain not found', 404); } - $response->dynamic($domain, Response::MODEL_DOMAIN); + $response->dynamic2($domain, Response::MODEL_DOMAIN); }); App::patch('/v1/projects/:projectId/domains/:domainId/verification') @@ -1655,18 +1526,18 @@ App::patch('/v1/projects/:projectId/domains/:domainId/verification') ->param('projectId', null, new UID(), 'Project unique ID.') ->param('domainId', null, new UID(), 'Domain unique ID.') ->inject('response') - ->inject('consoleDB') - ->action(function ($projectId, $domainId, $response, $consoleDB) { + ->inject('dbForConsole') + ->action(function ($projectId, $domainId, $response, $dbForConsole) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $consoleDB */ + /** @var Utopia\Database\Database $dbForConsole */ - $project = $consoleDB->getDocument($projectId); + $project = $dbForConsole->getDocument('projects', $projectId); - if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { + if ($project->isEmpty()) { throw new Exception('Project not found', 404); } - $domain = $project->search('$id', $domainId, $project->getAttribute('domains', [])); + $domain = $project->find('$id', $domainId, 'domains'); if (empty($domain) || !$domain instanceof Document) { throw new Exception('Domain not found', 404); @@ -1679,23 +1550,20 @@ App::patch('/v1/projects/:projectId/domains/:domainId/verification') } if ($domain->getAttribute('verification') === true) { - return $response->dynamic($domain, Response::MODEL_DOMAIN); + return $response->dynamic2($domain, Response::MODEL_DOMAIN); } - // Verify Domain with DNS records - $validator = new CNAME($target->get()); + $validator = new CNAME($target->get()); // Verify Domain with DNS records if (!$validator->isValid($domain->getAttribute('domain', ''))) { throw new Exception('Failed to verify domain', 401); } - $domain + $project->findAndReplace('$id', $domain->getId(), $domain ->setAttribute('verification', true) - ; + , 'domains'); - if (false === $consoleDB->updateDocument($domain->getArrayCopy())) { - throw new Exception('Failed saving domains to DB', 500); - } + $dbForConsole->updateDocument('projects', $project->getId(), $project); // Issue a TLS certificate when domain is verified Resque::enqueue('v1-certificates', 'CertificatesV1', [ @@ -1703,7 +1571,7 @@ App::patch('/v1/projects/:projectId/domains/:domainId/verification') 'domain' => $domain->getAttribute('domain'), ]); - $response->dynamic($domain, Response::MODEL_DOMAIN); + $response->dynamic2($domain, Response::MODEL_DOMAIN); }); App::delete('/v1/projects/:projectId/domains/:domainId') @@ -1718,32 +1586,30 @@ App::delete('/v1/projects/:projectId/domains/:domainId') ->param('projectId', null, new UID(), 'Project unique ID.') ->param('domainId', null, new UID(), 'Domain unique ID.') ->inject('response') - ->inject('consoleDB') + ->inject('dbForConsole') ->inject('deletes') - ->action(function ($projectId, $domainId, $response, $consoleDB, $deletes) { + ->action(function ($projectId, $domainId, $response, $dbForConsole, $deletes) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $consoleDB */ + /** @var Utopia\Database\Database $dbForConsole */ - $project = $consoleDB->getDocument($projectId); + $project = $dbForConsole->getDocument('projects', $projectId); - if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { + if ($project->isEmpty()) { throw new Exception('Project not found', 404); } - $domain = $project->search('$id', $domainId, $project->getAttribute('domains', [])); + $domain = $project->find('$id', $domainId, 'domains'); - if (empty($domain) || !$domain instanceof Document) { + if (!$project->findAndRemove('$id', $domainId, 'domains')) { throw new Exception('Domain not found', 404); } - if ($consoleDB->deleteDocument($domain->getId())) { - $deletes - ->setParam('type', DELETE_TYPE_CERTIFICATES) - ->setParam('document', $domain) - ; - } else { - throw new Exception('Failed to remove domains from DB', 500); - } + $dbForConsole->updateDocument('projects', $project->getId(), $project); + + $deletes + ->setParam('type', DELETE_TYPE_CERTIFICATES) + ->setParam('document', $domain) + ; $response->noContent(); }); \ No newline at end of file diff --git a/app/controllers/general.php b/app/controllers/general.php index 233dfd691c..76bd18fc16 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -17,6 +17,7 @@ use Appwrite\Network\Validator\Origin; use Appwrite\Utopia\Response\Filters\V06; use Appwrite\Utopia\Response\Filters\V07; use Utopia\CLI\Console; +use Utopia\Database\Document as Document2; use Utopia\Database\Validator\Authorization as Authorization2; Config::setParam('domainVerification', false); @@ -28,11 +29,11 @@ App::init(function ($utopia, $request, $response, $console, $project, $consoleDB /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $consoleDB */ /** @var Appwrite\Database\Document $console */ - /** @var Appwrite\Database\Document $project */ - /** @var Appwrite\Database\Document $user */ + /** @var Utopia\Database\Document $project */ + /** @var Utopia\Database\Document $user */ /** @var Utopia\Locale\Locale $locale */ /** @var array $clients */ - + $domain = $request->getHostname(); $domains = Config::getParam('domains', []); if (!array_key_exists($domain, $domains)) { @@ -129,8 +130,8 @@ App::init(function ($utopia, $request, $response, $console, $project, $consoleDB ); /* - * Response format - */ + * Response format + */ $responseFormat = $request->getHeader('x-appwrite-response-format', App::getEnv('_APP_SYSTEM_RESPONSE_FORMAT', '')); if ($responseFormat) { switch($responseFormat) { @@ -192,7 +193,7 @@ App::init(function ($utopia, $request, $response, $console, $project, $consoleDB $role = ($user->isEmpty()) ? Auth::USER_ROLE_GUEST : Auth::USER_ROLE_MEMBER; // Add user roles - $membership = $user->search('teamId', $project->getAttribute('teamId', null), $user->getAttribute('memberships', [])); + $membership = $user->find('teamId', $project->getAttribute('teamId', null), 'memberships'); if ($membership) { foreach ($membership->getAttribute('roles', []) as $memberRole) { @@ -218,14 +219,14 @@ App::init(function ($utopia, $request, $response, $console, $project, $consoleDB if (!empty($authKey)) { // API Key authentication // Check if given key match project API keys - $key = $project->search('secret', $authKey, $project->getAttribute('keys', [])); + $key = $project->find('secret', $authKey, 'keys'); /* * Try app auth when we have project key and no user * Mock user to app and grant API key scopes in addition to default app scopes */ if ($key && $user->isEmpty()) { - $user = new Document([ + $user = new Document2([ '$id' => '', 'status' => Auth::USER_STATUS_ACTIVATED, 'email' => 'app.'.$project->getId().'@service.'.$request->getHostname(), diff --git a/app/init.php b/app/init.php index f25e02fafe..2593177ca8 100644 --- a/app/init.php +++ b/app/init.php @@ -33,6 +33,7 @@ use PDO as PDONative; use Utopia\Cache\Adapter\Redis as RedisCache; use Utopia\Cache\Cache; use Utopia\Database\Adapter\MariaDB; +use Utopia\Database\Document as Document2; use Utopia\Database\Database as Database2; use Utopia\Database\Validator\Authorization as Authorization2; @@ -455,18 +456,17 @@ App::setResource('user', function($mode, $project, $console, $request, $response ; } - if (empty($user->getId()) // Check a document has been found in the DB - || Database::SYSTEM_COLLECTION_USERS !== $user->getCollection() // Validate returned document is really a user document + if ($user->isEmpty() // Check a document has been found in the DB || !Auth::sessionVerify($user->getAttribute('sessions', []), Auth::$secret)) { // Validate user has valid login token - $user = new Document(['$id' => '', '$collection' => Database::SYSTEM_COLLECTION_USERS]); + $user = new Document2(['$id' => '', '$collection' => 'users']); } if (APP_MODE_ADMIN === $mode) { - if (!empty($user->search('teamId', $project->getAttribute('teamId'), $user->getAttribute('memberships')))) { + if (!$user->find('teamId', $project->getAttribute('teamId'), 'memberships')) { Authorization::setDefaultStatus(false); // Cancel security segmentation for admin users. Authorization2::setDefaultStatus(false); // Cancel security segmentation for admin users. } else { - $user = new Document(['$id' => '', '$collection' => Database::SYSTEM_COLLECTION_USERS]); + $user = new Document2(['$id' => '', '$collection' => 'users']); } } @@ -488,8 +488,8 @@ App::setResource('user', function($mode, $project, $console, $request, $response $user = $dbForInternal->getDocument('users', $jwtUserId); } - if (empty($user->search('$id', $jwtSessionId, $user->getAttribute('sessions')))) { // Match JWT to active token - $user = new Document(['$id' => '', '$collection' => 'users']); + if (empty($user->find('$id', $jwtSessionId, 'sessions'))) { // Match JWT to active token + $user = new Document2(['$id' => '', '$collection' => 'users']); } } diff --git a/composer.json b/composer.json index c28af87716..9f36f6e3ad 100644 --- a/composer.json +++ b/composer.json @@ -45,7 +45,7 @@ "utopia-php/cache": "0.4.*", "utopia-php/cli": "0.11.*", "utopia-php/config": "0.2.*", - "utopia-php/database": "v0.x-dev", + "utopia-php/database": "dev-feat-new-doc-methods", "utopia-php/locale": "0.3.*", "utopia-php/registry": "0.4.*", "utopia-php/preloader": "0.2.*", diff --git a/composer.lock b/composer.lock index 350246bd89..c043ae9805 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "e1d74c5addc00532630652439c858347", + "content-hash": "1d24d85bcbddad6834435b58286703ca", "packages": [ { "name": "adhocore/jwt", @@ -1919,11 +1919,11 @@ }, { "name": "utopia-php/database", - "version": "v0.x-dev", + "version": "dev-feat-new-doc-methods", "source": { "type": "git", "url": "https://github.com/utopia-php/database", - "reference": "d54bbae93fea9f60df535e4f669146e24d87761b" + "reference": "fe6e0ca2ff26b721516a8db83bff378f428a2caf" }, "require": { "ext-mongodb": "*", @@ -1970,7 +1970,7 @@ "upf", "utopia" ], - "time": "2021-05-09T13:42:23+00:00" + "time": "2021-05-15T15:00:12+00:00" }, { "name": "utopia-php/domains", @@ -5235,16 +5235,16 @@ }, { "name": "symfony/console", - "version": "v5.2.7", + "version": "v5.2.8", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "90374b8ed059325b49a29b55b3f8bb4062c87629" + "reference": "864568fdc0208b3eba3638b6000b69d2386e6768" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/90374b8ed059325b49a29b55b3f8bb4062c87629", - "reference": "90374b8ed059325b49a29b55b3f8bb4062c87629", + "url": "https://api.github.com/repos/symfony/console/zipball/864568fdc0208b3eba3638b6000b69d2386e6768", + "reference": "864568fdc0208b3eba3638b6000b69d2386e6768", "shasum": "" }, "require": { @@ -5312,7 +5312,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.2.7" + "source": "https://github.com/symfony/console/tree/v5.2.8" }, "funding": [ { @@ -5328,7 +5328,7 @@ "type": "tidelift" } ], - "time": "2021-04-19T14:07:32+00:00" + "time": "2021-05-11T15:45:21+00:00" }, { "name": "symfony/polyfill-intl-grapheme", @@ -5735,16 +5735,16 @@ }, { "name": "symfony/string", - "version": "v5.2.6", + "version": "v5.2.8", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "ad0bd91bce2054103f5eaa18ebeba8d3bc2a0572" + "reference": "01b35eb64cac8467c3f94cd0ce2d0d376bb7d1db" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/ad0bd91bce2054103f5eaa18ebeba8d3bc2a0572", - "reference": "ad0bd91bce2054103f5eaa18ebeba8d3bc2a0572", + "url": "https://api.github.com/repos/symfony/string/zipball/01b35eb64cac8467c3f94cd0ce2d0d376bb7d1db", + "reference": "01b35eb64cac8467c3f94cd0ce2d0d376bb7d1db", "shasum": "" }, "require": { @@ -5798,7 +5798,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v5.2.6" + "source": "https://github.com/symfony/string/tree/v5.2.8" }, "funding": [ { @@ -5814,7 +5814,7 @@ "type": "tidelift" } ], - "time": "2021-03-17T17:12:15+00:00" + "time": "2021-05-10T14:56:10+00:00" }, { "name": "theseer/tokenizer", @@ -5868,16 +5868,16 @@ }, { "name": "twig/twig", - "version": "v2.14.4", + "version": "v2.14.5", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "0b4ba691fb99ec7952d25deb36c0a83061b93bbf" + "reference": "c9dd15b3a80725bc4919730fae462bddcc960820" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/0b4ba691fb99ec7952d25deb36c0a83061b93bbf", - "reference": "0b4ba691fb99ec7952d25deb36c0a83061b93bbf", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/c9dd15b3a80725bc4919730fae462bddcc960820", + "reference": "c9dd15b3a80725bc4919730fae462bddcc960820", "shasum": "" }, "require": { @@ -5931,7 +5931,7 @@ ], "support": { "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v2.14.4" + "source": "https://github.com/twigphp/Twig/tree/v2.14.5" }, "funding": [ { @@ -5943,7 +5943,7 @@ "type": "tidelift" } ], - "time": "2021-03-10T10:05:55+00:00" + "time": "2021-05-12T08:02:35+00:00" }, { "name": "vimeo/psalm", From 2760cf5993099da868830c4310556aafdfe91d9a Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sun, 16 May 2021 12:18:34 +0300 Subject: [PATCH 28/60] Cleanups - work in progress --- app/controllers/api/projects.php | 5 +++ app/controllers/api/users.php | 4 +- app/controllers/general.php | 12 +++--- app/controllers/shared/api.php | 70 ++++++++++++++++---------------- app/init.php | 67 ++++++++++++++++++++++++------ 5 files changed, 100 insertions(+), 58 deletions(-) diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index aeaa87be91..ed8100c80a 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -77,6 +77,11 @@ App::post('/v1/projects') 'keys' => [], 'tasks' => [], 'domains' => [], + 'usersAuthEmailPassword' => true, + 'usersAuthAnonymous' => true, + 'usersAuthInvites' => true, + 'usersAuthJWT' => true, + 'usersAuthPhone' => true, ])); if (false === $project) { diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 882aba83de..420fc1b0ec 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -449,9 +449,7 @@ App::delete('/v1/users/:userId/sessions') throw new Exception('User not found', 404); } - $sessions = $user->getAttribute('sessions', []); - - $dbForInternal->updateDocument('users', $user->getId(), $user); + $dbForInternal->updateDocument('users', $user->getId(), $user->getAttribute('sessions', [])); $events ->setParam('eventData', $response->output2($user, Response::MODEL_USER)) diff --git a/app/controllers/general.php b/app/controllers/general.php index 76bd18fc16..266744cf0d 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -10,14 +10,12 @@ use Utopia\Exception; use Utopia\Config\Config; use Utopia\Domains\Domain; use Appwrite\Auth\Auth; -use Appwrite\Database\Database; -use Appwrite\Database\Document; use Appwrite\Database\Validator\Authorization; use Appwrite\Network\Validator\Origin; use Appwrite\Utopia\Response\Filters\V06; use Appwrite\Utopia\Response\Filters\V07; use Utopia\CLI\Console; -use Utopia\Database\Document as Document2; +use Utopia\Database\Document; use Utopia\Database\Validator\Authorization as Authorization2; Config::setParam('domainVerification', false); @@ -28,7 +26,7 @@ App::init(function ($utopia, $request, $response, $console, $project, $consoleDB /** @var Utopia\Swoole\Request $request */ /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $consoleDB */ - /** @var Appwrite\Database\Document $console */ + /** @var Utopia\Database\Document $console */ /** @var Utopia\Database\Document $project */ /** @var Utopia\Database\Document $user */ /** @var Utopia\Locale\Locale $locale */ @@ -226,7 +224,7 @@ App::init(function ($utopia, $request, $response, $console, $project, $consoleDB * Mock user to app and grant API key scopes in addition to default app scopes */ if ($key && $user->isEmpty()) { - $user = new Document2([ + $user = new Document([ '$id' => '', 'status' => Auth::USER_STATUS_ACTIVATED, 'email' => 'app.'.$project->getId().'@service.'.$request->getHostname(), @@ -265,7 +263,7 @@ App::init(function ($utopia, $request, $response, $console, $project, $consoleDB // TDOO Check if user is root if (!\in_array($scope, $scopes)) { - if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS !== $project->getCollection()) { // Check if permission is denied because project is missing + if ($project->isEmpty()) { // Check if permission is denied because project is missing throw new Exception('Project not found', 404); } @@ -386,7 +384,7 @@ App::error(function ($error, $utopia, $request, $response, $layout, $project) { $response->html($layout->render()); } - $response->dynamic(new Document($output), + $response->dynamic2(new Document($output), $utopia->isDevelopment() ? Response::MODEL_ERROR_DEV : Response::MODEL_ERROR); }, ['error', 'utopia', 'request', 'response', 'layout', 'project']); diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index 8b3971079d..211d0774b3 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -13,8 +13,8 @@ App::init(function ($utopia, $request, $response, $project, $user, $register, $e /** @var Utopia\App $utopia */ /** @var Utopia\Swoole\Request $request */ /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Document $project */ - /** @var Appwrite\Database\Document $user */ + /** @var Utopia\Database\Document $project */ + /** @var Utopia\Database\Document $user */ /** @var Utopia\Registry\Registry $register */ /** @var Appwrite\Event\Event $events */ /** @var Appwrite\Event\Event $audits */ @@ -31,45 +31,45 @@ App::init(function ($utopia, $request, $response, $project, $user, $register, $e throw new Exception('Missing or unknown project ID', 400); } - /* - * Abuse Check - */ - $timeLimit = new TimeLimit($route->getLabel('abuse-key', 'url:{url},ip:{ip}'), $route->getLabel('abuse-limit', 0), $route->getLabel('abuse-time', 3600), function () use ($register) { - return $register->get('db'); - }); - $timeLimit->setNamespace('app_'.$project->getId()); - $timeLimit - ->setParam('{userId}', $user->getId()) - ->setParam('{userAgent}', $request->getUserAgent('')) - ->setParam('{ip}', $request->getIP()) - ->setParam('{url}', $request->getHostname().$route->getURL()) - ; + // /* + // * Abuse Check + // */ + // $timeLimit = new TimeLimit($route->getLabel('abuse-key', 'url:{url},ip:{ip}'), $route->getLabel('abuse-limit', 0), $route->getLabel('abuse-time', 3600), function () use ($register) { + // return $register->get('db'); + // }); + // $timeLimit->setNamespace('app_'.$project->getId()); + // $timeLimit + // ->setParam('{userId}', $user->getId()) + // ->setParam('{userAgent}', $request->getUserAgent('')) + // ->setParam('{ip}', $request->getIP()) + // ->setParam('{url}', $request->getHostname().$route->getURL()) + // ; - //TODO make sure we get array here + // //TODO make sure we get array here - foreach ($request->getParams() as $key => $value) { // Set request params as potential abuse keys - $timeLimit->setParam('{param-'.$key.'}', (\is_array($value)) ? \json_encode($value) : $value); - } + // foreach ($request->getParams() as $key => $value) { // Set request params as potential abuse keys + // $timeLimit->setParam('{param-'.$key.'}', (\is_array($value)) ? \json_encode($value) : $value); + // } - $abuse = new Abuse($timeLimit); + // $abuse = new Abuse($timeLimit); - if ($timeLimit->limit()) { - $response - ->addHeader('X-RateLimit-Limit', $timeLimit->limit()) - ->addHeader('X-RateLimit-Remaining', $timeLimit->remaining()) - ->addHeader('X-RateLimit-Reset', $timeLimit->time() + $route->getLabel('abuse-time', 3600)) - ; - } + // if ($timeLimit->limit()) { + // $response + // ->addHeader('X-RateLimit-Limit', $timeLimit->limit()) + // ->addHeader('X-RateLimit-Remaining', $timeLimit->remaining()) + // ->addHeader('X-RateLimit-Reset', $timeLimit->time() + $route->getLabel('abuse-time', 3600)) + // ; + // } - $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::$roles); - $isAppUser = Auth::isAppUser(Authorization::$roles); + // $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::$roles); + // $isAppUser = Auth::isAppUser(Authorization::$roles); - if (($abuse->check() // Route is rate-limited - && App::getEnv('_APP_OPTIONS_ABUSE', 'enabled') !== 'disabled') // Abuse is not diabled - && (!$isAppUser && !$isPrivilegedUser)) // User is not an admin or API key - { - throw new Exception('Too many requests', 429); - } + // if (($abuse->check() // Route is rate-limited + // && App::getEnv('_APP_OPTIONS_ABUSE', 'enabled') !== 'disabled') // Abuse is not diabled + // && (!$isAppUser && !$isPrivilegedUser)) // User is not an admin or API key + // { + // throw new Exception('Too many requests', 429); + // } /* * Background Jobs diff --git a/app/init.php b/app/init.php index 2593177ca8..a5ebac13de 100644 --- a/app/init.php +++ b/app/init.php @@ -445,15 +445,11 @@ App::setResource('user', function($mode, $project, $console, $request, $response Auth::$unique = $session['id'] ?? ''; Auth::$secret = $session['secret'] ?? ''; - if (APP_MODE_ADMIN !== $mode) { + if (APP_MODE_ADMIN !== $mode && $project->getId() !== 'console') { $user = $dbForInternal->getDocument('users', Auth::$unique); } else { $user = $dbForConsole->getDocument('users', Auth::$unique); - - $user - ->setAttribute('$id', 'admin-'.$user->getAttribute('$id')) - ; } if ($user->isEmpty() // Check a document has been found in the DB @@ -496,25 +492,70 @@ App::setResource('user', function($mode, $project, $console, $request, $response return $user; }, ['mode', 'project', 'console', 'request', 'response', 'dbForInternal', 'dbForConsole']); -App::setResource('project', function($consoleDB, $request) { +App::setResource('project', function($dbForConsole, $request, $console) { /** @var Utopia\Swoole\Request $request */ - /** @var Appwrite\Database\Database $consoleDB */ + /** @var Appwrite\Database\Database $dbForConsole */ + /** @var Appwrite\Database\Document $console */ + + $projectId = $request->getParam('project', + $request->getHeader('x-appwrite-project', '')); + + if(empty($projectId) || $projectId === 'console') { + return $console; + } Authorization::disable(); Authorization2::disable(); - $project = $consoleDB->getDocument($request->getParam('project', - $request->getHeader('x-appwrite-project', 'console'))); + $project = $dbForConsole->getDocument('projects', $projectId); Authorization::reset(); Authorization2::reset(); return $project; -}, ['consoleDB', 'request']); +}, ['dbForConsole', 'request', 'console']); -App::setResource('console', function($consoleDB) { - return $consoleDB->getDocument('console'); -}, ['consoleDB']); +App::setResource('console', function() { + return new Document2([ + '$id' => 'console', + '$collection' => 'projects', + 'name' => 'Appwrite', + 'description' => 'Appwrite core engine', + 'logo' => '', + 'teamId' => -1, + 'webhooks' => [], + 'keys' => [], + 'platforms' => [ + [ + '$collection' => Database::SYSTEM_COLLECTION_PLATFORMS, + 'name' => 'Production', + 'type' => 'web', + 'hostname' => 'appwrite.io', + ], + [ + '$collection' => Database::SYSTEM_COLLECTION_PLATFORMS, + 'name' => 'Development', + 'type' => 'web', + 'hostname' => 'appwrite.test', + ], + [ + '$collection' => Database::SYSTEM_COLLECTION_PLATFORMS, + 'name' => 'Localhost', + 'type' => 'web', + 'hostname' => 'localhost', + ], // Current host is added on app init + ], + 'legalName' => '', + 'legalCountry' => '', + 'legalState' => '', + 'legalCity' => '', + 'legalAddress' => '', + 'legalTaxId' => '', + 'authWhitelistEmails' => (!empty(App::getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null))) ? \explode(',', App::getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null)) : [], + 'authWhitelistIPs' => (!empty(App::getEnv('_APP_CONSOLE_WHITELIST_IPS', null))) ? \explode(',', App::getEnv('_APP_CONSOLE_WHITELIST_IPS', null)) : [], + 'usersAuthLimit' => (App::getEnv('_APP_CONSOLE_WHITELIST_ROOT', 'enabled') === 'enabled') ? 1 : 0, // limit signup to 1 user + ]); +}, []); App::setResource('consoleDB', function($register) { $consoleDB = new Database(); From cee71ef7b31c00bfb86ffbba081cd2fdbc972a13 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sun, 16 May 2021 13:55:12 +0300 Subject: [PATCH 29/60] Fixed permission check --- app/controllers/api/projects.php | 4 ---- app/controllers/general.php | 6 +++--- app/init.php | 8 ++++---- composer.lock | 4 ++-- 4 files changed, 9 insertions(+), 13 deletions(-) diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index ed8100c80a..74a4a489cf 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -84,10 +84,6 @@ App::post('/v1/projects') 'usersAuthPhone' => true, ])); - if (false === $project) { - throw new Exception('Failed saving project to DB', 500); - } - $collections = Config::getParam('collections2', []); /** @var array $collections */ $dbForInternal->setNamespace('project_'.$project->getId().'_internal'); diff --git a/app/controllers/general.php b/app/controllers/general.php index 266744cf0d..a87304b412 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -191,10 +191,10 @@ App::init(function ($utopia, $request, $response, $console, $project, $consoleDB $role = ($user->isEmpty()) ? Auth::USER_ROLE_GUEST : Auth::USER_ROLE_MEMBER; // Add user roles - $membership = $user->find('teamId', $project->getAttribute('teamId', null), 'memberships'); + $memberships = $user->find('teamId', $project->getAttribute('teamId', null), 'memberships'); - if ($membership) { - foreach ($membership->getAttribute('roles', []) as $memberRole) { + if ($memberships) { + foreach ($memberships->getAttribute('roles', []) as $memberRole) { switch ($memberRole) { case 'owner': $role = Auth::USER_ROLE_OWNER; diff --git a/app/init.php b/app/init.php index a5ebac13de..1e4a725694 100644 --- a/app/init.php +++ b/app/init.php @@ -445,7 +445,7 @@ App::setResource('user', function($mode, $project, $console, $request, $response Auth::$unique = $session['id'] ?? ''; Auth::$secret = $session['secret'] ?? ''; - if (APP_MODE_ADMIN !== $mode && $project->getId() !== 'console') { + if (APP_MODE_ADMIN !== $mode) { $user = $dbForInternal->getDocument('users', Auth::$unique); } else { @@ -458,7 +458,7 @@ App::setResource('user', function($mode, $project, $console, $request, $response } if (APP_MODE_ADMIN === $mode) { - if (!$user->find('teamId', $project->getAttribute('teamId'), 'memberships')) { + if ($user->find('teamId', $project->getAttribute('teamId'), 'memberships')) { Authorization::setDefaultStatus(false); // Cancel security segmentation for admin users. Authorization2::setDefaultStatus(false); // Cancel security segmentation for admin users. } else { @@ -498,9 +498,9 @@ App::setResource('project', function($dbForConsole, $request, $console) { /** @var Appwrite\Database\Document $console */ $projectId = $request->getParam('project', - $request->getHeader('x-appwrite-project', '')); + $request->getHeader('x-appwrite-project', 'console')); - if(empty($projectId) || $projectId === 'console') { + if($projectId === 'console') { return $console; } diff --git a/composer.lock b/composer.lock index c043ae9805..da26eafe4f 100644 --- a/composer.lock +++ b/composer.lock @@ -1923,7 +1923,7 @@ "source": { "type": "git", "url": "https://github.com/utopia-php/database", - "reference": "fe6e0ca2ff26b721516a8db83bff378f428a2caf" + "reference": "d4bd2ab55c87b290bd241b437c3095d945dcbf2f" }, "require": { "ext-mongodb": "*", @@ -1970,7 +1970,7 @@ "upf", "utopia" ], - "time": "2021-05-15T15:00:12+00:00" + "time": "2021-05-15T22:41:08+00:00" }, { "name": "utopia-php/domains", From f2976a82ad88f8973154922ccbaf965bfd304e32 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Mon, 17 May 2021 12:37:33 +0300 Subject: [PATCH 30/60] Fixed auth --- app/init.php | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/app/init.php b/app/init.php index 1e4a725694..4d609231c1 100644 --- a/app/init.php +++ b/app/init.php @@ -446,7 +446,12 @@ App::setResource('user', function($mode, $project, $console, $request, $response Auth::$secret = $session['secret'] ?? ''; if (APP_MODE_ADMIN !== $mode) { - $user = $dbForInternal->getDocument('users', Auth::$unique); + if ($project->isEmpty()) { + $user = new Document2(['$id' => '', '$collection' => 'users']); + } + else { + $user = $dbForInternal->getDocument('users', Auth::$unique); + } } else { $user = $dbForConsole->getDocument('users', Auth::$unique); @@ -468,7 +473,7 @@ App::setResource('user', function($mode, $project, $console, $request, $response $authJWT = $request->getHeader('x-appwrite-jwt', ''); - if (!empty($authJWT)) { // JWT authentication + if (!empty($authJWT) && !$project->isEmpty()) { // JWT authentication $jwt = new JWT(App::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 900, 10); // Instantiate with key, algo, maxAge and leeway. try { @@ -494,8 +499,8 @@ App::setResource('user', function($mode, $project, $console, $request, $response App::setResource('project', function($dbForConsole, $request, $console) { /** @var Utopia\Swoole\Request $request */ - /** @var Appwrite\Database\Database $dbForConsole */ - /** @var Appwrite\Database\Document $console */ + /** @var Utopia\Database\Database $dbForConsole */ + /** @var Utopia\Database\Document $console */ $projectId = $request->getParam('project', $request->getHeader('x-appwrite-project', 'console')); From c91c5cfbac981593769296d766e036f0124820d0 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Mon, 17 May 2021 12:37:48 +0300 Subject: [PATCH 31/60] Added project exists check --- app/controllers/general.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/controllers/general.php b/app/controllers/general.php index a87304b412..d912d4770f 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -85,7 +85,11 @@ App::init(function ($utopia, $request, $response, $console, $project, $consoleDB $route = $utopia->match($request); - if (!empty($route->getLabel('sdk.auth', [])) && empty($project->getId()) && ($route->getLabel('scope', '') !== 'public')) { + if ($project->isEmpty()) { + throw new Exception('Project not found', 404); + } + + if (!empty($route->getLabel('sdk.auth', [])) && $project->isEmpty() && ($route->getLabel('scope', '') !== 'public')) { throw new Exception('Missing or unknown project ID', 400); } From b93a5227b68909044be5ed0911fde10edd7d27ba Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Mon, 17 May 2021 12:38:03 +0300 Subject: [PATCH 32/60] Fixed auth redirect --- app/controllers/web/home.php | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/app/controllers/web/home.php b/app/controllers/web/home.php index 15b10a2f1b..6bbe0d1424 100644 --- a/app/controllers/web/home.php +++ b/app/controllers/web/home.php @@ -43,12 +43,12 @@ App::get('/') ->label('permission', 'public') ->label('scope', 'home') ->inject('response') - ->inject('consoleDB') + ->inject('dbForConsole') ->inject('project') - ->action(function ($response, $consoleDB, $project) { + ->action(function ($response, $dbForConsole, $project) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $consoleDB */ - /** @var Appwrite\Database\Document $project */ + /** @var Utopia\Database\Database $dbForConsole */ + /** @var Utopia\Database\Document $project */ $response ->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0') @@ -60,13 +60,7 @@ App::get('/') $whitlistRoot = App::getEnv('_APP_CONSOLE_WHITELIST_ROOT', 'enabled'); if($whitlistRoot !== 'disabled') { - $consoleDB->getCollection([ // Count users - 'filters' => [ - '$collection='.Database::SYSTEM_COLLECTION_USERS, - ], - ]); - - $sum = $consoleDB->getSum(); + $sum = $dbForConsole->count('users', [], APP_LIMIT_USERS); if($sum !== 0) { return $response->redirect('/auth/signin'); From aee35dbffeaa7f5ded832a0c615abb0386b114ce Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Mon, 17 May 2021 12:43:10 +0300 Subject: [PATCH 33/60] Fixed webhooks tests - work in progress --- tests/e2e/Services/Webhooks/WebhooksBase.php | 32 ++++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/tests/e2e/Services/Webhooks/WebhooksBase.php b/tests/e2e/Services/Webhooks/WebhooksBase.php index b6734b3b87..ac14514d3d 100644 --- a/tests/e2e/Services/Webhooks/WebhooksBase.php +++ b/tests/e2e/Services/Webhooks/WebhooksBase.php @@ -56,10 +56,10 @@ trait WebhooksBase $this->assertNotEmpty($webhook['data']['$id']); $this->assertEquals($webhook['data']['name'], 'Actors'); $this->assertIsArray($webhook['data']['$permissions']); - $this->assertIsArray($webhook['data']['$permissions']['read']); - $this->assertIsArray($webhook['data']['$permissions']['write']); - $this->assertCount(1, $webhook['data']['$permissions']['read']); - $this->assertCount(1, $webhook['data']['$permissions']['write']); + $this->assertIsArray($webhook['data']['$read']); + $this->assertIsArray($webhook['data']['$write']); + $this->assertCount(1, $webhook['data']['$read']); + $this->assertCount(1, $webhook['data']['$write']); $this->assertCount(2, $webhook['data']['rules']); return array_merge(['actorsId' => $actors['body']['$id']]); @@ -99,10 +99,10 @@ trait WebhooksBase $this->assertNotEmpty($webhook['data']['$id']); $this->assertEquals($webhook['data']['firstName'], 'Chris'); $this->assertEquals($webhook['data']['lastName'], 'Evans'); - $this->assertIsArray($webhook['data']['$permissions']['read']); - $this->assertIsArray($webhook['data']['$permissions']['write']); - $this->assertCount(1, $webhook['data']['$permissions']['read']); - $this->assertCount(1, $webhook['data']['$permissions']['write']); + $this->assertIsArray($webhook['data']['$read']); + $this->assertIsArray($webhook['data']['$write']); + $this->assertCount(1, $webhook['data']['$read']); + $this->assertCount(1, $webhook['data']['$write']); $data['documentId'] = $document['body']['$id']; @@ -142,10 +142,10 @@ trait WebhooksBase $this->assertNotEmpty($webhook['data']['$id']); $this->assertEquals($webhook['data']['firstName'], 'Chris1'); $this->assertEquals($webhook['data']['lastName'], 'Evans2'); - $this->assertIsArray($webhook['data']['$permissions']['read']); - $this->assertIsArray($webhook['data']['$permissions']['write']); - $this->assertCount(1, $webhook['data']['$permissions']['read']); - $this->assertCount(1, $webhook['data']['$permissions']['write']); + $this->assertIsArray($webhook['data']['$read']); + $this->assertIsArray($webhook['data']['$write']); + $this->assertCount(1, $webhook['data']['$read']); + $this->assertCount(1, $webhook['data']['$write']); return $data; } @@ -191,10 +191,10 @@ trait WebhooksBase $this->assertNotEmpty($webhook['data']['$id']); $this->assertEquals($webhook['data']['firstName'], 'Bradly'); $this->assertEquals($webhook['data']['lastName'], 'Cooper'); - $this->assertIsArray($webhook['data']['$permissions']['read']); - $this->assertIsArray($webhook['data']['$permissions']['write']); - $this->assertCount(1, $webhook['data']['$permissions']['read']); - $this->assertCount(1, $webhook['data']['$permissions']['write']); + $this->assertIsArray($webhook['data']['$read']); + $this->assertIsArray($webhook['data']['$write']); + $this->assertCount(1, $webhook['data']['$read']); + $this->assertCount(1, $webhook['data']['$write']); return $data; } From 26d0d5d3dc1fa48056bff2be9f2deeb26c2134cf Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Thu, 27 May 2021 13:09:14 +0300 Subject: [PATCH 34/60] Implemented search --- app/config/collections2.php | 31 +++++++++++++++++- app/controllers/api/account.php | 4 +-- app/controllers/api/database.php | 14 +++----- app/controllers/api/functions.php | 51 ++++++++++++----------------- app/controllers/api/health.php | 3 +- app/controllers/api/projects.php | 49 ++++++++++++---------------- app/controllers/api/storage.php | 12 ++++--- app/controllers/api/teams.php | 10 +++--- app/controllers/api/users.php | 4 +-- composer.json | 2 +- composer.lock | 53 +++++++++++++++++-------------- 11 files changed, 125 insertions(+), 108 deletions(-) diff --git a/app/config/collections2.php b/app/config/collections2.php index bf942d5c40..79aa3637ca 100644 --- a/app/config/collections2.php +++ b/app/config/collections2.php @@ -184,6 +184,13 @@ $collections = [ ], ], 'indexes' => [ + [ + '$id' => '_fulltext_name', + 'type' => Database::INDEX_FULLTEXT, + 'attributes' => ['name'], + 'lengths' => [1024], + 'orders' => [Database::ORDER_ASC], + ] ], ], @@ -360,7 +367,15 @@ $collections = [ 'filters' => [], ], ], - 'indexes' => [], + 'indexes' => [ + [ + '$id' => '_fulltext_name', + 'type' => Database::INDEX_FULLTEXT, + 'attributes' => ['name'], + 'lengths' => [1024], + 'orders' => [Database::ORDER_ASC], + ] + ], ], 'memberships' => [ @@ -618,6 +633,13 @@ $collections = [ 'attributes' => ['bucketId'], 'lengths' => [Database::LENGTH_KEY], 'orders' => [Database::ORDER_ASC], + ], + [ + '$id' => '_fulltext_name', + 'type' => Database::INDEX_FULLTEXT, + 'attributes' => ['name'], + 'lengths' => [1024], + 'orders' => [Database::ORDER_ASC], ] ], ], @@ -760,6 +782,13 @@ $collections = [ ], ], 'indexes' => [ + [ + '$id' => '_fulltext_name', + 'type' => Database::INDEX_FULLTEXT, + 'attributes' => ['name'], + 'lengths' => [1024], + 'orders' => [Database::ORDER_ASC], + ] ], ], diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 486d66b8c4..c6f53faec9 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -810,8 +810,8 @@ App::get('/v1/account/sessions') } $response->dynamic2(new Document([ + 'sessions' => $sessions, 'sum' => count($sessions), - 'sessions' => $sessions ]), Response::MODEL_SESSION_LIST); }); @@ -1250,8 +1250,8 @@ App::delete('/v1/account/sessions') $events ->setParam('eventData', $response->output2(new Document([ + 'sessions' => $sessions, 'sum' => count($sessions), - 'sessions' => $sessions ]), Response::MODEL_SESSION_LIST)) ; diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php index 5af0e6ab4e..d4cad710cb 100644 --- a/app/controllers/api/database.php +++ b/app/controllers/api/database.php @@ -85,10 +85,8 @@ App::post('/v1/database/collections') ->setParam('data', $data->getArrayCopy()) ; - $response - ->setStatusCode(Response::STATUS_CODE_CREATED) - ->dynamic($data, Response::MODEL_COLLECTION) - ; + $response->setStatusCode(Response::STATUS_CODE_CREATED); + $response->dynamic($data, Response::MODEL_COLLECTION); }); App::get('/v1/database/collections') @@ -123,8 +121,8 @@ App::get('/v1/database/collections') ]); $response->dynamic(new Document([ + 'collections' => $results, 'sum' => $projectDB->getSum(), - 'collections' => $results ]), Response::MODEL_COLLECTION_LIST); }); @@ -399,10 +397,8 @@ App::post('/v1/database/collections/:collectionId/documents') ->setParam('data', $data->getArrayCopy()) ; - $response - ->setStatusCode(Response::STATUS_CODE_CREATED) - ->dynamic($data, Response::MODEL_DOCUMENT) - ; + $response->setStatusCode(Response::STATUS_CODE_CREATED); + $response->dynamic($data, Response::MODEL_DOCUMENT); }); App::get('/v1/database/collections/:collectionId/documents') diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index 6a2e48d5cb..10ad3dece4 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -66,10 +66,8 @@ App::post('/v1/functions') 'timeout' => $timeout, ])); - $response - ->setStatusCode(Response::STATUS_CODE_CREATED) - ->dynamic2($function, Response::MODEL_FUNCTION) - ; + $response->setStatusCode(Response::STATUS_CODE_CREATED); + $response->dynamic2($function, Response::MODEL_FUNCTION); }); App::get('/v1/functions') @@ -93,9 +91,11 @@ App::get('/v1/functions') /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForInternal */ + $queries = ($search) ? [new Query('name', Query::TYPE_SEARCH, [$search])] : []; + $response->dynamic2(new Document([ - 'sum' => $dbForInternal->count('functions', [], APP_LIMIT_COUNT), - 'functions' => $dbForInternal->find('functions', [], $limit, $offset) + 'functions' => $dbForInternal->find('functions', $queries, $limit, $offset, ['_id'], [$orderType]), + 'sum' => $dbForInternal->count('functions', $queries, APP_LIMIT_COUNT), ]), Response::MODEL_FUNCTION_LIST); }); @@ -483,10 +483,8 @@ App::post('/v1/functions/:functionId/tags') ->setParam('storage', $tag->getAttribute('size', 0)) ; - $response - ->setStatusCode(Response::STATUS_CODE_CREATED) - ->dynamic2($tag, Response::MODEL_TAG) - ; + $response->setStatusCode(Response::STATUS_CODE_CREATED); + $response->dynamic2($tag, Response::MODEL_TAG); }); App::get('/v1/functions/:functionId/tags') @@ -501,13 +499,13 @@ App::get('/v1/functions/:functionId/tags') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_TAG_LIST) ->param('functionId', '', new UID(), 'Function unique ID.') - ->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true) + // ->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true) ->param('limit', 25, new Range(0, 100), 'Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true) ->param('offset', 0, new Range(0, 2000), 'Results offset. The default value is 0. Use this param to manage pagination.', true) - ->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true) + // ->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true) ->inject('response') ->inject('dbForInternal') - ->action(function ($functionId, $search, $limit, $offset, $orderType, $response, $dbForInternal) { + ->action(function ($functionId, $limit, $offset, $response, $dbForInternal) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForInternal */ @@ -516,18 +514,15 @@ App::get('/v1/functions/:functionId/tags') if (empty($function->getId())) { throw new Exception('Function not found', 404); } + + $queries[] = new Query('functionId', Query::TYPE_EQUAL, [$function->getId()]); - $results = $dbForInternal->find('tags', [ - new Query('functionId', Query::TYPE_EQUAL, [$function->getId()]), - ], $limit, $offset); - - $sum = $dbForInternal->count('tags', [ - new Query('functionId', Query::TYPE_EQUAL, [$function->getId()]), - ], APP_LIMIT_COUNT); + $results = $dbForInternal->find('tags', $queries, $limit, $offset); + $sum = $dbForInternal->count('tags', $queries, APP_LIMIT_COUNT); $response->dynamic2(new Document([ + 'tags' => $results, 'sum' => $sum, - 'tags' => $results ]), Response::MODEL_TAG_LIST); }); @@ -730,10 +725,8 @@ App::post('/v1/functions/:functionId/executions') 'jwt' => $jwt, ]); - $response - ->setStatusCode(Response::STATUS_CODE_CREATED) - ->dynamic2($execution, Response::MODEL_EXECUTION) - ; + $response->setStatusCode(Response::STATUS_CODE_CREATED); + $response->dynamic2($execution, Response::MODEL_EXECUTION); }); App::get('/v1/functions/:functionId/executions') @@ -748,13 +741,11 @@ App::get('/v1/functions/:functionId/executions') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_EXECUTION_LIST) ->param('functionId', '', new UID(), 'Function unique ID.') - ->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true) ->param('limit', 25, new Range(0, 100), 'Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true) ->param('offset', 0, new Range(0, 2000), 'Results offset. The default value is 0. Use this param to manage pagination.', true) - ->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true) ->inject('response') ->inject('dbForInternal') - ->action(function ($functionId, $search, $limit, $offset, $orderType, $response, $dbForInternal) { + ->action(function ($functionId, $limit, $offset, $response, $dbForInternal) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForInternal */ @@ -765,7 +756,7 @@ App::get('/v1/functions/:functionId/executions') if (empty($function->getId())) { throw new Exception('Function not found', 404); } - + $results = $dbForInternal->find('executions', [ new Query('functionId', Query::TYPE_EQUAL, [$function->getId()]), ], $limit, $offset); @@ -775,8 +766,8 @@ App::get('/v1/functions/:functionId/executions') ], APP_LIMIT_COUNT); $response->dynamic2(new Document([ + 'executions' => $results, 'sum' => $sum, - 'executions' => $results ]), Response::MODEL_EXECUTION_LIST); }); diff --git a/app/controllers/api/health.php b/app/controllers/api/health.php index 0885c0c40b..991ebd552a 100644 --- a/app/controllers/api/health.php +++ b/app/controllers/api/health.php @@ -6,7 +6,6 @@ use Utopia\Storage\Device\Local; use Utopia\Storage\Storage; use Appwrite\ClamAV\Network; use Appwrite\Event\Event; -use RuntimeException; App::get('/v1/health') ->desc('Get HTTP') @@ -268,7 +267,7 @@ App::get('/v1/health/anti-virus') 'status' => (@$antiVirus->ping()) ? 'online' : 'offline', 'version' => @$antiVirus->version(), ]); - } catch( RuntimeException $e) { + } catch (Throwable $e) { $response->json([ 'status' => 'offline', 'version' => '', diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index 74a4a489cf..0fa28f16ea 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -18,6 +18,7 @@ use Appwrite\Network\Validator\Domain as DomainValidator; use Appwrite\Utopia\Response; use Cron\CronExpression; use Utopia\Database\Document; +use Utopia\Database\Query; use Utopia\Database\Validator\UID; App::post('/v1/projects') @@ -143,12 +144,14 @@ App::get('/v1/projects') /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForConsole */ - $results = $dbForConsole->find('projects', [], $limit, $offset); - $sum = $dbForConsole->count('projects', [], APP_LIMIT_COUNT); + $queries = ($search) ? [new Query('name', Query::TYPE_SEARCH, [$search])] : []; + + $results = $dbForConsole->find('projects', $queries, $limit, $offset); + $sum = $dbForConsole->count('projects', $queries, APP_LIMIT_COUNT); $response->dynamic2(new Document([ + 'projects' => $results, 'sum' => $sum, - 'projects' => $results ]), Response::MODEL_PROJECT_LIST); }); @@ -622,10 +625,8 @@ App::post('/v1/projects/:projectId/webhooks') ->setAttribute('webhooks', $webhook, Document::SET_TYPE_APPEND) ); - $response - ->setStatusCode(Response::STATUS_CODE_CREATED) - ->dynamic2($webhook, Response::MODEL_WEBHOOK) - ; + $response->setStatusCode(Response::STATUS_CODE_CREATED); + $response->dynamic2($webhook, Response::MODEL_WEBHOOK); }); App::get('/v1/projects/:projectId/webhooks') @@ -654,8 +655,8 @@ App::get('/v1/projects/:projectId/webhooks') $webhooks = $project->getAttribute('webhooks', []); $response->dynamic2(new Document([ + 'webhooks' => $webhooks, 'sum' => count($webhooks), - 'webhooks' => $webhooks ]), Response::MODEL_WEBHOOK_LIST); }); @@ -814,10 +815,8 @@ App::post('/v1/projects/:projectId/keys') ->setAttribute('keys', $key, Document::SET_TYPE_APPEND) ); - $response - ->setStatusCode(Response::STATUS_CODE_CREATED) - ->dynamic2($key, Response::MODEL_KEY) - ; + $response->setStatusCode(Response::STATUS_CODE_CREATED); + $response->dynamic2($key, Response::MODEL_KEY); }); App::get('/v1/projects/:projectId/keys') @@ -846,8 +845,8 @@ App::get('/v1/projects/:projectId/keys') $keys = $project->getAttribute('keys', []); $response->dynamic2(new Document([ + 'keys' => $keys, 'sum' => count($keys), - 'keys' => $keys ]), Response::MODEL_KEY_LIST); }); @@ -1019,10 +1018,8 @@ App::post('/v1/projects/:projectId/tasks') ResqueScheduler::enqueueAt($next, 'v1-tasks', 'TasksV1', $task->getArrayCopy()); } - $response - ->setStatusCode(Response::STATUS_CODE_CREATED) - ->dynamic2($task, Response::MODEL_TASK) - ; + $response->setStatusCode(Response::STATUS_CODE_CREATED); + $response->dynamic2($task, Response::MODEL_TASK); }); App::get('/v1/projects/:projectId/tasks') @@ -1051,8 +1048,8 @@ App::get('/v1/projects/:projectId/tasks') $tasks = $project->getAttribute('tasks', []); $response->dynamic2(new Document([ + 'tasks' => $tasks, 'sum' => count($tasks), - 'tasks' => $tasks ]), Response::MODEL_TASK_LIST); }); @@ -1233,10 +1230,8 @@ App::post('/v1/projects/:projectId/platforms') ->setAttribute('platforms', $platform, Document::SET_TYPE_APPEND) ); - $response - ->setStatusCode(Response::STATUS_CODE_CREATED) - ->dynamic2($platform, Response::MODEL_PLATFORM) - ; + $response->setStatusCode(Response::STATUS_CODE_CREATED); + $response->dynamic2($platform, Response::MODEL_PLATFORM); }); App::get('/v1/projects/:projectId/platforms') @@ -1265,8 +1260,8 @@ App::get('/v1/projects/:projectId/platforms') $platforms = $project->getAttribute('platforms', []); $response->dynamic2(new Document([ + 'platforms' => $platforms, 'sum' => count($platforms), - 'platforms' => $platforms ]), Response::MODEL_PLATFORM_LIST); }); @@ -1444,10 +1439,8 @@ App::post('/v1/projects/:projectId/domains') ->setAttribute('domains', $domain, Document::SET_TYPE_APPEND) ); - $response - ->setStatusCode(Response::STATUS_CODE_CREATED) - ->dynamic2($domain, Response::MODEL_DOMAIN) - ; + $response->setStatusCode(Response::STATUS_CODE_CREATED); + $response->dynamic2($domain, Response::MODEL_DOMAIN); }); App::get('/v1/projects/:projectId/domains') @@ -1476,8 +1469,8 @@ App::get('/v1/projects/:projectId/domains') $domains = $project->getAttribute('domains', []); $response->dynamic2(new Document([ + 'domains' => $domains, 'sum' => count($domains), - 'domains' => $domains ]), Response::MODEL_DOMAIN_LIST); }); diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index f847dcdc51..246209f9a5 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -21,6 +21,7 @@ use Utopia\Image\Image; use Appwrite\OpenSSL\OpenSSL; use Appwrite\Utopia\Response; use Utopia\Config\Config; +use Utopia\Database\Query; use Utopia\Validator\Numeric; App::post('/v1/storage/files') @@ -149,9 +150,8 @@ App::post('/v1/storage/files') ->setParam('storage', $sizeActual) ; - $response - ->setStatusCode(Response::STATUS_CODE_CREATED) - ->dynamic2($file, Response::MODEL_FILE) + $response->setStatusCode(Response::STATUS_CODE_CREATED); + $response->dynamic2($file, Response::MODEL_FILE); ; }); @@ -176,9 +176,11 @@ App::get('/v1/storage/files') /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForInternal */ + $queries = ($search) ? [new Query('name', Query::TYPE_SEARCH, $search)] : []; + $response->dynamic2(new Document([ - 'sum' => $dbForInternal->count('files', [], APP_LIMIT_COUNT), - 'files' => $dbForInternal->find('files', [], $limit, $offset) + 'files' => $dbForInternal->find('files', $queries, $limit, $offset, ['_id'], [$orderType]), + 'sum' => $dbForInternal->count('files', $queries, APP_LIMIT_COUNT), ]), Response::MODEL_FILE_LIST); }); diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index e65f850c31..5b4a2c2a64 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -104,12 +104,14 @@ App::get('/v1/teams') /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForInternal */ - $results = $dbForInternal->find('teams', [], $limit, $offset); - $sum = $dbForInternal->count('teams', [], APP_LIMIT_COUNT); + $queries = ($search) ? [new Query('name', Query::TYPE_SEARCH, [$search])] : []; + + $results = $dbForInternal->find('teams', $queries, $limit, $offset, ['_id'], [$orderType]); + $sum = $dbForInternal->count('teams', $queries, APP_LIMIT_COUNT); $response->dynamic2(new Document([ + 'teams' => $results, 'sum' => $sum, - 'teams' => $results ]), Response::MODEL_TEAM_LIST); }); @@ -449,8 +451,8 @@ App::get('/v1/teams/:teamId/memberships') } $response->dynamic2(new Document([ + 'memberships' => $users, 'sum' => $sum, - 'memberships' => $users ]), Response::MODEL_MEMBERSHIP_LIST); }); diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 420fc1b0ec..fa8344d48a 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -91,8 +91,8 @@ App::get('/v1/users') $sum = $dbForInternal->count('users', [], APP_LIMIT_COUNT); $response->dynamic2(new Document([ + 'users' => $results, 'sum' => $sum, - 'users' => $results ]), Response::MODEL_USER_LIST); }); @@ -193,8 +193,8 @@ App::get('/v1/users/:userId/sessions') } $response->dynamic2(new Document([ + 'sessions' => $sessions, 'sum' => count($sessions), - 'sessions' => $sessions ]), Response::MODEL_SESSION_LIST); }, ['response', 'dbForInternal', 'locale']); diff --git a/composer.json b/composer.json index a255d26d51..9f79a0d328 100644 --- a/composer.json +++ b/composer.json @@ -45,7 +45,7 @@ "utopia-php/cache": "0.4.*", "utopia-php/cli": "0.11.*", "utopia-php/config": "0.2.*", - "utopia-php/database": "dev-feat-new-doc-methods", + "utopia-php/database": "dev-feat-mariadb-fulltext-support", "utopia-php/locale": "0.3.*", "utopia-php/registry": "0.4.*", "utopia-php/preloader": "0.2.*", diff --git a/composer.lock b/composer.lock index 488023ac0b..c0a242d22e 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "1d24d85bcbddad6834435b58286703ca", + "content-hash": "d9816fe1eb62d29f71193b590b547171", "packages": [ { "name": "adhocore/jwt", @@ -355,16 +355,16 @@ }, { "name": "composer/package-versions-deprecated", - "version": "1.11.99.1", + "version": "1.11.99.2", "source": { "type": "git", "url": "https://github.com/composer/package-versions-deprecated.git", - "reference": "7413f0b55a051e89485c5cb9f765fe24bb02a7b6" + "reference": "c6522afe5540d5fc46675043d3ed5a45a740b27c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/7413f0b55a051e89485c5cb9f765fe24bb02a7b6", - "reference": "7413f0b55a051e89485c5cb9f765fe24bb02a7b6", + "url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/c6522afe5540d5fc46675043d3ed5a45a740b27c", + "reference": "c6522afe5540d5fc46675043d3ed5a45a740b27c", "shasum": "" }, "require": { @@ -408,7 +408,7 @@ "description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)", "support": { "issues": "https://github.com/composer/package-versions-deprecated/issues", - "source": "https://github.com/composer/package-versions-deprecated/tree/1.11.99.1" + "source": "https://github.com/composer/package-versions-deprecated/tree/1.11.99.2" }, "funding": [ { @@ -424,7 +424,7 @@ "type": "tidelift" } ], - "time": "2020-11-11T10:22:58+00:00" + "time": "2021-05-24T07:46:03+00:00" }, { "name": "dragonmantank/cron-expression", @@ -1919,11 +1919,17 @@ }, { "name": "utopia-php/database", - "version": "dev-feat-new-doc-methods", + "version": "dev-feat-mariadb-fulltext-support", "source": { "type": "git", - "url": "https://github.com/utopia-php/database", - "reference": "d4bd2ab55c87b290bd241b437c3095d945dcbf2f" + "url": "https://github.com/utopia-php/database.git", + "reference": "aa3f364d1a1140e9707bfeecc2dbbb29bb75fdef" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/utopia-php/database/zipball/aa3f364d1a1140e9707bfeecc2dbbb29bb75fdef", + "reference": "aa3f364d1a1140e9707bfeecc2dbbb29bb75fdef", + "shasum": "" }, "require": { "ext-mongodb": "*", @@ -1944,11 +1950,7 @@ "Utopia\\Database\\": "src/Database" } }, - "autoload-dev": { - "psr-4": { - "Utopia\\Tests\\": "tests/Database" - } - }, + "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], @@ -1970,7 +1972,11 @@ "upf", "utopia" ], - "time": "2021-05-15T22:41:08+00:00" + "support": { + "issues": "https://github.com/utopia-php/database/issues", + "source": "https://github.com/utopia-php/database/tree/feat-mariadb-fulltext-support" + }, + "time": "2021-05-18T07:51:42+00:00" }, { "name": "utopia-php/domains", @@ -5879,16 +5885,16 @@ }, { "name": "twig/twig", - "version": "v2.14.5", + "version": "v2.14.6", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "c9dd15b3a80725bc4919730fae462bddcc960820" + "reference": "27e5cf2b05e3744accf39d4c68a3235d9966d260" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/c9dd15b3a80725bc4919730fae462bddcc960820", - "reference": "c9dd15b3a80725bc4919730fae462bddcc960820", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/27e5cf2b05e3744accf39d4c68a3235d9966d260", + "reference": "27e5cf2b05e3744accf39d4c68a3235d9966d260", "shasum": "" }, "require": { @@ -5942,7 +5948,7 @@ ], "support": { "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v2.14.5" + "source": "https://github.com/twigphp/Twig/tree/v2.14.6" }, "funding": [ { @@ -5954,7 +5960,7 @@ "type": "tidelift" } ], - "time": "2021-05-12T08:02:35+00:00" + "time": "2021-05-16T12:12:47+00:00" }, { "name": "vimeo/psalm", @@ -6115,8 +6121,7 @@ "aliases": [], "minimum-stability": "stable", "stability-flags": { - "utopia-php/database": 20, - "appwrite/sdk-generator": 20 + "utopia-php/database": 20 }, "prefer-stable": false, "prefer-lowest": false, From 78cc0ae3b8a61427bd756cc9e213ea37c59b8d58 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Thu, 27 May 2021 14:22:18 +0300 Subject: [PATCH 35/60] Added transaction support --- composer.json | 2 +- composer.lock | 102 ++++++++++++++++++------------------ src/Appwrite/Extend/PDO.php | 36 +++++++++++++ 3 files changed, 87 insertions(+), 53 deletions(-) diff --git a/composer.json b/composer.json index 9f79a0d328..c5cf7a271d 100644 --- a/composer.json +++ b/composer.json @@ -45,7 +45,7 @@ "utopia-php/cache": "0.4.*", "utopia-php/cli": "0.11.*", "utopia-php/config": "0.2.*", - "utopia-php/database": "dev-feat-mariadb-fulltext-support", + "utopia-php/database": "0.2.*", "utopia-php/locale": "0.3.*", "utopia-php/registry": "0.4.*", "utopia-php/preloader": "0.2.*", diff --git a/composer.lock b/composer.lock index c0a242d22e..efed72c2d6 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "d9816fe1eb62d29f71193b590b547171", + "content-hash": "5cf39daf305902a168233757d4a00066", "packages": [ { "name": "adhocore/jwt", @@ -1441,16 +1441,16 @@ }, { "name": "symfony/polyfill-ctype", - "version": "v1.22.1", + "version": "v1.23.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "c6c942b1ac76c82448322025e084cadc56048b4e" + "reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/c6c942b1ac76c82448322025e084cadc56048b4e", - "reference": "c6c942b1ac76c82448322025e084cadc56048b4e", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/46cd95797e9df938fdd2b03693b5fca5e64b01ce", + "reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce", "shasum": "" }, "require": { @@ -1462,7 +1462,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.22-dev" + "dev-main": "1.23-dev" }, "thanks": { "name": "symfony/polyfill", @@ -1500,7 +1500,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.22.1" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.23.0" }, "funding": [ { @@ -1516,20 +1516,20 @@ "type": "tidelift" } ], - "time": "2021-01-07T16:49:33+00:00" + "time": "2021-02-19T12:13:01+00:00" }, { "name": "symfony/polyfill-php80", - "version": "v1.22.1", + "version": "v1.23.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "dc3063ba22c2a1fd2f45ed856374d79114998f91" + "reference": "eca0bf41ed421bed1b57c4958bab16aa86b757d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/dc3063ba22c2a1fd2f45ed856374d79114998f91", - "reference": "dc3063ba22c2a1fd2f45ed856374d79114998f91", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/eca0bf41ed421bed1b57c4958bab16aa86b757d0", + "reference": "eca0bf41ed421bed1b57c4958bab16aa86b757d0", "shasum": "" }, "require": { @@ -1538,7 +1538,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.22-dev" + "dev-main": "1.23-dev" }, "thanks": { "name": "symfony/polyfill", @@ -1583,7 +1583,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.22.1" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.23.0" }, "funding": [ { @@ -1599,7 +1599,7 @@ "type": "tidelift" } ], - "time": "2021-01-07T16:49:33+00:00" + "time": "2021-02-19T12:13:01+00:00" }, { "name": "utopia-php/abuse", @@ -1919,16 +1919,16 @@ }, { "name": "utopia-php/database", - "version": "dev-feat-mariadb-fulltext-support", + "version": "0.2.0", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "aa3f364d1a1140e9707bfeecc2dbbb29bb75fdef" + "reference": "b5dd144d582f3355c13f5430b1b3d7eb850bc5cd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/aa3f364d1a1140e9707bfeecc2dbbb29bb75fdef", - "reference": "aa3f364d1a1140e9707bfeecc2dbbb29bb75fdef", + "url": "https://api.github.com/repos/utopia-php/database/zipball/b5dd144d582f3355c13f5430b1b3d7eb850bc5cd", + "reference": "b5dd144d582f3355c13f5430b1b3d7eb850bc5cd", "shasum": "" }, "require": { @@ -1974,9 +1974,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/feat-mariadb-fulltext-support" + "source": "https://github.com/utopia-php/database/tree/0.2.0" }, - "time": "2021-05-18T07:51:42+00:00" + "time": "2021-05-26T18:41:44+00:00" }, { "name": "utopia-php/domains", @@ -5349,16 +5349,16 @@ }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.22.1", + "version": "v1.23.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "5601e09b69f26c1828b13b6bb87cb07cddba3170" + "reference": "24b72c6baa32c746a4d0840147c9715e42bb68ab" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/5601e09b69f26c1828b13b6bb87cb07cddba3170", - "reference": "5601e09b69f26c1828b13b6bb87cb07cddba3170", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/24b72c6baa32c746a4d0840147c9715e42bb68ab", + "reference": "24b72c6baa32c746a4d0840147c9715e42bb68ab", "shasum": "" }, "require": { @@ -5370,7 +5370,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.22-dev" + "dev-main": "1.23-dev" }, "thanks": { "name": "symfony/polyfill", @@ -5410,7 +5410,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.22.1" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.23.0" }, "funding": [ { @@ -5426,20 +5426,20 @@ "type": "tidelift" } ], - "time": "2021-01-22T09:19:47+00:00" + "time": "2021-05-27T09:17:38+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.22.1", + "version": "v1.23.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "43a0283138253ed1d48d352ab6d0bdb3f809f248" + "reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/43a0283138253ed1d48d352ab6d0bdb3f809f248", - "reference": "43a0283138253ed1d48d352ab6d0bdb3f809f248", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8590a5f561694770bdcd3f9b5c69dde6945028e8", + "reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8", "shasum": "" }, "require": { @@ -5451,7 +5451,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.22-dev" + "dev-main": "1.23-dev" }, "thanks": { "name": "symfony/polyfill", @@ -5494,7 +5494,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.22.1" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.23.0" }, "funding": [ { @@ -5510,20 +5510,20 @@ "type": "tidelift" } ], - "time": "2021-01-22T09:19:47+00:00" + "time": "2021-02-19T12:13:01+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.22.1", + "version": "v1.23.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "5232de97ee3b75b0360528dae24e73db49566ab1" + "reference": "2df51500adbaebdc4c38dea4c89a2e131c45c8a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/5232de97ee3b75b0360528dae24e73db49566ab1", - "reference": "5232de97ee3b75b0360528dae24e73db49566ab1", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/2df51500adbaebdc4c38dea4c89a2e131c45c8a1", + "reference": "2df51500adbaebdc4c38dea4c89a2e131c45c8a1", "shasum": "" }, "require": { @@ -5535,7 +5535,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.22-dev" + "dev-main": "1.23-dev" }, "thanks": { "name": "symfony/polyfill", @@ -5574,7 +5574,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.22.1" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.23.0" }, "funding": [ { @@ -5590,20 +5590,20 @@ "type": "tidelift" } ], - "time": "2021-01-22T09:19:47+00:00" + "time": "2021-05-27T09:27:20+00:00" }, { "name": "symfony/polyfill-php73", - "version": "v1.22.1", + "version": "v1.23.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "a678b42e92f86eca04b7fa4c0f6f19d097fb69e2" + "reference": "fba8933c384d6476ab14fb7b8526e5287ca7e010" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/a678b42e92f86eca04b7fa4c0f6f19d097fb69e2", - "reference": "a678b42e92f86eca04b7fa4c0f6f19d097fb69e2", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/fba8933c384d6476ab14fb7b8526e5287ca7e010", + "reference": "fba8933c384d6476ab14fb7b8526e5287ca7e010", "shasum": "" }, "require": { @@ -5612,7 +5612,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.22-dev" + "dev-main": "1.23-dev" }, "thanks": { "name": "symfony/polyfill", @@ -5653,7 +5653,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php73/tree/v1.22.1" + "source": "https://github.com/symfony/polyfill-php73/tree/v1.23.0" }, "funding": [ { @@ -5669,7 +5669,7 @@ "type": "tidelift" } ], - "time": "2021-01-07T16:49:33+00:00" + "time": "2021-02-19T12:13:01+00:00" }, { "name": "symfony/service-contracts", @@ -6120,9 +6120,7 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": { - "utopia-php/database": 20 - }, + "stability-flags": [], "prefer-stable": false, "prefer-lowest": false, "platform": { diff --git a/src/Appwrite/Extend/PDO.php b/src/Appwrite/Extend/PDO.php index 72f9f37ac3..0d25131bfb 100644 --- a/src/Appwrite/Extend/PDO.php +++ b/src/Appwrite/Extend/PDO.php @@ -59,6 +59,42 @@ class PDO extends PDONative return $this->pdo->quote($string, $parameter_type); } + public function beginTransaction() + { + try { + $result = $this->pdo->beginTransaction(); + } catch (\Throwable $th) { + $this->pdo = $this->reconnect(); + $result = $this->pdo->beginTransaction(); + } + + return $result; + } + + public function rollBack() + { + try { + $result = $this->pdo->rollBack(); + } catch (\Throwable $th) { + $this->pdo = $this->reconnect(); + $result = $this->pdo->rollBack(); + } + + return $result; + } + + public function commit() + { + try { + $result = $this->pdo->commit(); + } catch (\Throwable $th) { + $this->pdo = $this->reconnect(); + $result = $this->pdo->commit(); + } + + return $result; + } + public function reconnect(): PDONative { $this->pdo = new PDONative($this->dsn, $this->username, $this->passwd, $this->options); From 7720e2cdf9869f4a088c9a456fb27606cf02a33c Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Thu, 27 May 2021 17:57:54 +0300 Subject: [PATCH 36/60] Updated test --- tests/e2e/Services/Account/AccountCustomClientTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/e2e/Services/Account/AccountCustomClientTest.php b/tests/e2e/Services/Account/AccountCustomClientTest.php index 77a4f93686..f64c7dc780 100644 --- a/tests/e2e/Services/Account/AccountCustomClientTest.php +++ b/tests/e2e/Services/Account/AccountCustomClientTest.php @@ -243,7 +243,6 @@ class AccountCustomClientTest extends Scope $this->assertIsArray($response['body']); $this->assertNotEmpty($response['body']); $this->assertNotEmpty($response['body']['$id']); - $this->assertNotEmpty($response['body']['userId']); $session = $this->client->parseCookie((string)$response['headers']['set-cookie'])['a_session_'.$this->getProject()['$id']]; From d4868860a1f20b157bbf1281be22882c59e73b08 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Thu, 27 May 2021 17:58:07 +0300 Subject: [PATCH 37/60] Fixed rollback --- src/Appwrite/Extend/PDO.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Appwrite/Extend/PDO.php b/src/Appwrite/Extend/PDO.php index 0d25131bfb..d5b2cab94c 100644 --- a/src/Appwrite/Extend/PDO.php +++ b/src/Appwrite/Extend/PDO.php @@ -77,7 +77,6 @@ class PDO extends PDONative $result = $this->pdo->rollBack(); } catch (\Throwable $th) { $this->pdo = $this->reconnect(); - $result = $this->pdo->rollBack(); } return $result; From a0be399f0c5926b56625dab265f62324b8570332 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sun, 6 Jun 2021 20:54:01 +0300 Subject: [PATCH 38/60] Fix db connection --- app/controllers/api/projects.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index 69e0a93314..c754c4a2d0 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -52,7 +52,7 @@ App::post('/v1/projects') /** @var Utopia\Database\Database $dbForInternal */ /** @var Utopia\Database\Database $dbForExternal */ - $team = $dbForInternal->getDocument('teams', $teamId); + $team = $dbForConsole->getDocument('teams', $teamId); if ($team->isEmpty()) { throw new Exception('Team not found', 404); From 12e01f809237b5ad8b803699d30113387d3bbaea Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 7 Jun 2021 11:02:29 +0545 Subject: [PATCH 39/60] integrate abuse and audit --- app/controllers/api/account.php | 12 +- app/controllers/api/projects.php | 183 +++++++++++++++---------------- app/controllers/api/users.php | 10 +- app/controllers/shared/api.php | 72 ++++++------ app/workers/audits.php | 11 +- app/workers/deletes.php | 31 ++++-- composer.json | 14 ++- composer.lock | 143 +++++++++++++++++------- 8 files changed, 280 insertions(+), 196 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 11f4e86a48..91be0299bd 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -21,7 +21,6 @@ use Utopia\Audit\Audit; use Utopia\Audit\Adapters\MySQL as AuditAdapter; use Utopia\Database\Document; use Utopia\Database\Exception\Duplicate; -use Ahc\Jwt\JWT; use Utopia\Database\Query; use Utopia\Database\Validator\Authorization; use Utopia\Database\Validator\UID; @@ -830,22 +829,19 @@ App::get('/v1/account/logs') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_LOG_LIST) ->inject('response') - ->inject('register') - ->inject('project') ->inject('user') ->inject('locale') ->inject('geodb') - ->action(function ($response, $register, $project, $user, $locale, $geodb) { + ->inject('dbForInternal') + ->action(function ($response, $user, $locale, $geodb, $dbForInternal) { /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Document $project */ /** @var Utopia\Database\Document $user */ /** @var Utopia\Locale\Locale $locale */ /** @var MaxMind\Db\Reader $geodb */ + /** @var Utopia\Database\Database $dbForInternal */ - $adapter = new AuditAdapter($register->get('db')); - $adapter->setNamespace('app_'.$project->getId()); - - $audit = new Audit($adapter); + $audit = new Audit($dbForInternal); $countries = $locale->getText('countries'); $logs = $audit->getLogsByUserAndActions($user->getId(), [ diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index 69e0a93314..6af20880a9 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -1,25 +1,25 @@ desc('Create Project') @@ -60,8 +60,8 @@ App::post('/v1/projects') $project = $dbForConsole->createDocument('projects', new Document([ '$collection' => 'projects', - '$read' => ['team:'.$teamId], - '$write' => ['team:'.$teamId.'/owner', 'team:'.$teamId.'/developer'], + '$read' => ['team:' . $teamId], + '$write' => ['team:' . $teamId . '/owner', 'team:' . $teamId . '/developer'], 'name' => $name, 'description' => $description, 'logo' => $logo, @@ -87,9 +87,9 @@ App::post('/v1/projects') $collections = Config::getParam('collections2', []); /** @var array $collections */ - $dbForInternal->setNamespace('project_'.$project->getId().'_internal'); + $dbForInternal->setNamespace('project_' . $project->getId() . '_internal'); $dbForInternal->create(); - $dbForExternal->setNamespace('project_'.$project->getId().'_external'); + $dbForExternal->setNamespace('project_' . $project->getId() . '_external'); $dbForExternal->create(); foreach ($collections as $key => $collection) { @@ -206,7 +206,7 @@ App::get('/v1/projects/:projectId/usage') throw new Exception('Project not found', 404); } - if(App::getEnv('_APP_USAGE_STATS', 'enabled') == 'enabled') { + if (App::getEnv('_APP_USAGE_STATS', 'enabled') == 'enabled') { $period = [ '24h' => [ @@ -230,44 +230,44 @@ App::get('/v1/projects/:projectId/usage') 'group' => '1d', ], ]; - + $client = $register->get('influxdb'); - + $requests = []; $network = []; $functions = []; - + if ($client) { $start = $period[$range]['start']->format(DateTime::RFC3339); $end = $period[$range]['end']->format(DateTime::RFC3339); $database = $client->selectDB('telegraf'); - + // Requests - $result = $database->query('SELECT sum(value) AS "value" FROM "appwrite_usage_requests_all" WHERE time > \''.$start.'\' AND time < \''.$end.'\' AND "metric_type"=\'counter\' AND "project"=\''.$project->getId().'\' GROUP BY time('.$period[$range]['group'].') FILL(null)'); + $result = $database->query('SELECT sum(value) AS "value" FROM "appwrite_usage_requests_all" WHERE time > \'' . $start . '\' AND time < \'' . $end . '\' AND "metric_type"=\'counter\' AND "project"=\'' . $project->getId() . '\' GROUP BY time(' . $period[$range]['group'] . ') FILL(null)'); $points = $result->getPoints(); - + foreach ($points as $point) { $requests[] = [ 'value' => (!empty($point['value'])) ? $point['value'] : 0, 'date' => \strtotime($point['time']), ]; } - + // Network - $result = $database->query('SELECT sum(value) AS "value" FROM "appwrite_usage_network_all" WHERE time > \''.$start.'\' AND time < \''.$end.'\' AND "metric_type"=\'counter\' AND "project"=\''.$project->getId().'\' GROUP BY time('.$period[$range]['group'].') FILL(null)'); + $result = $database->query('SELECT sum(value) AS "value" FROM "appwrite_usage_network_all" WHERE time > \'' . $start . '\' AND time < \'' . $end . '\' AND "metric_type"=\'counter\' AND "project"=\'' . $project->getId() . '\' GROUP BY time(' . $period[$range]['group'] . ') FILL(null)'); $points = $result->getPoints(); - + foreach ($points as $point) { $network[] = [ 'value' => (!empty($point['value'])) ? $point['value'] : 0, 'date' => \strtotime($point['time']), ]; } - + // Functions - $result = $database->query('SELECT sum(value) AS "value" FROM "appwrite_usage_executions_all" WHERE time > \''.$start.'\' AND time < \''.$end.'\' AND "metric_type"=\'counter\' AND "project"=\''.$project->getId().'\' GROUP BY time('.$period[$range]['group'].') FILL(null)'); + $result = $database->query('SELECT sum(value) AS "value" FROM "appwrite_usage_executions_all" WHERE time > \'' . $start . '\' AND time < \'' . $end . '\' AND "metric_type"=\'counter\' AND "project"=\'' . $project->getId() . '\' GROUP BY time(' . $period[$range]['group'] . ') FILL(null)'); $points = $result->getPoints(); - + foreach ($points as $point) { $functions[] = [ 'value' => (!empty($point['value'])) ? $point['value'] : 0, @@ -281,14 +281,13 @@ App::get('/v1/projects/:projectId/usage') $functions = []; } - // Users $projectDB->getCollection([ 'limit' => 0, 'offset' => 0, 'filters' => [ - '$collection=users' + '$collection=users', ], ]); @@ -313,7 +312,7 @@ App::get('/v1/projects/:projectId/usage') 'limit' => 0, 'offset' => 0, 'filters' => [ - '$collection='.$collection['$id'], + '$collection=' . $collection['$id'], ], ]); @@ -369,7 +368,7 @@ App::get('/v1/projects/:projectId/usage') '$collection=files', ], ] - ) + + ) + $projectDB->getCount( [ 'attribute' => 'size', @@ -416,16 +415,16 @@ App::patch('/v1/projects/:projectId') } $project = $dbForConsole->updateDocument('projects', $project->getId(), $project - ->setAttribute('name', $name) - ->setAttribute('description', $description) - ->setAttribute('logo', $logo) - ->setAttribute('url', $url) - ->setAttribute('legalName', $legalName) - ->setAttribute('legalCountry', $legalCountry) - ->setAttribute('legalState', $legalState) - ->setAttribute('legalCity', $legalCity) - ->setAttribute('legalAddress', $legalAddress) - ->setAttribute('legalTaxId', $legalTaxId) + ->setAttribute('name', $name) + ->setAttribute('description', $description) + ->setAttribute('logo', $logo) + ->setAttribute('url', $url) + ->setAttribute('legalName', $legalName) + ->setAttribute('legalCountry', $legalCountry) + ->setAttribute('legalState', $legalState) + ->setAttribute('legalCity', $legalCity) + ->setAttribute('legalAddress', $legalAddress) + ->setAttribute('legalTaxId', $legalTaxId) ); $response->dynamic2($project, Response::MODEL_PROJECT); @@ -458,8 +457,8 @@ App::patch('/v1/projects/:projectId/oauth2') } $project = $dbForConsole->updateDocument('projects', $project->getId(), $project - ->setAttribute('usersOauth2'.\ucfirst($provider).'Appid', $appId) - ->setAttribute('usersOauth2'.\ucfirst($provider).'Secret', $secret) + ->setAttribute('usersOauth2' . \ucfirst($provider) . 'Appid', $appId) + ->setAttribute('usersOauth2' . \ucfirst($provider) . 'Secret', $secret) ); $response->dynamic2($project, Response::MODEL_PROJECT); @@ -490,7 +489,7 @@ App::patch('/v1/projects/:projectId/auth/limit') } $dbForConsole->updateDocument('projects', $project->getId(), $project - ->setAttribute('usersAuthLimit', $limit) + ->setAttribute('usersAuthLimit', $limit) ); $response->dynamic2($project, Response::MODEL_PROJECT); @@ -507,7 +506,7 @@ App::patch('/v1/projects/:projectId/auth/:method') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_PROJECT) ->param('projectId', '', new UID(), 'Project unique ID.') - ->param('method', '', new WhiteList(\array_keys(Config::getParam('auth')), true), 'Auth Method. Possible values: '.implode(',', \array_keys(Config::getParam('auth'))), false) + ->param('method', '', new WhiteList(\array_keys(Config::getParam('auth')), true), 'Auth Method. Possible values: ' . implode(',', \array_keys(Config::getParam('auth'))), false) ->param('status', false, new Boolean(true), 'Set the status of this auth method.') ->inject('response') ->inject('dbForConsole') @@ -525,7 +524,7 @@ App::patch('/v1/projects/:projectId/auth/:method') } $dbForConsole->updateDocument('projects', $project->getId(), $project - ->setAttribute($authKey, $status) + ->setAttribute($authKey, $status) ); $response->dynamic2($project, Response::MODEL_PROJECT); @@ -566,7 +565,7 @@ App::delete('/v1/projects/:projectId') ->setParam('type', DELETE_TYPE_DOCUMENT) ->setParam('document', $project) ; - + if (!$dbForConsole->deleteDocument('teams', $project->getAttribute('teamId', null))) { throw new Exception('Failed to remove project team from DB', 500); } @@ -622,7 +621,7 @@ App::post('/v1/projects/:projectId/webhooks') ]); $project = $dbForConsole->updateDocument('projects', $project->getId(), $project - ->setAttribute('webhooks', $webhook, Document::SET_TYPE_APPEND) + ->setAttribute('webhooks', $webhook, Document::SET_TYPE_APPEND) ); $response->setStatusCode(Response::STATUS_CODE_CREATED); @@ -732,16 +731,16 @@ App::put('/v1/projects/:projectId/webhooks/:webhookId') } $project->findAndReplace('$id', $webhook->getId(), $webhook - ->setAttribute('name', $name) - ->setAttribute('events', $events) - ->setAttribute('url', $url) - ->setAttribute('security', $security) - ->setAttribute('httpUser', $httpUser) - ->setAttribute('httpPass', $httpPass) - , 'webhooks'); + ->setAttribute('name', $name) + ->setAttribute('events', $events) + ->setAttribute('url', $url) + ->setAttribute('security', $security) + ->setAttribute('httpUser', $httpUser) + ->setAttribute('httpPass', $httpPass) + , 'webhooks'); $dbForConsole->updateDocument('projects', $project->getId(), $project); - + $response->dynamic2($webhook, Response::MODEL_WEBHOOK); }); @@ -812,7 +811,7 @@ App::post('/v1/projects/:projectId/keys') ]); $project = $dbForConsole->updateDocument('projects', $project->getId(), $project - ->setAttribute('keys', $key, Document::SET_TYPE_APPEND) + ->setAttribute('keys', $key, Document::SET_TYPE_APPEND) ); $response->setStatusCode(Response::STATUS_CODE_CREATED); @@ -835,7 +834,7 @@ App::get('/v1/projects/:projectId/keys') ->action(function ($projectId, $response, $dbForConsole) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForConsole */ - + $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { @@ -913,12 +912,12 @@ App::put('/v1/projects/:projectId/keys/:keyId') } $project->findAndReplace('$id', $key->getId(), $key - ->setAttribute('name', $name) - ->setAttribute('scopes', $scopes) - , 'keys'); + ->setAttribute('name', $name) + ->setAttribute('scopes', $scopes) + , 'keys'); $dbForConsole->updateDocument('projects', $project->getId(), $project); - + $response->dynamic2($key, Response::MODEL_KEY); }); @@ -1011,7 +1010,7 @@ App::post('/v1/projects/:projectId/tasks') ]); $project = $dbForConsole->updateDocument('projects', $project->getId(), $project - ->setAttribute('tasks', $task, Document::SET_TYPE_APPEND) + ->setAttribute('tasks', $task, Document::SET_TYPE_APPEND) ); if ($next) { @@ -1131,18 +1130,18 @@ App::put('/v1/projects/:projectId/tasks/:taskId') $security = ($security === '1' || $security === 'true' || $security === 1 || $security === true); $project->findAndReplace('$id', $task->getId(), $task - ->setAttribute('name', $name) - ->setAttribute('status', $status) - ->setAttribute('schedule', $schedule) - ->setAttribute('updated', \time()) - ->setAttribute('next', $next) - ->setAttribute('security', $security) - ->setAttribute('httpMethod', $httpMethod) - ->setAttribute('httpUrl', $httpUrl) - ->setAttribute('httpHeaders', $httpHeaders) - ->setAttribute('httpUser', $httpUser) - ->setAttribute('httpPass', $httpPass) - , 'tasks'); + ->setAttribute('name', $name) + ->setAttribute('status', $status) + ->setAttribute('schedule', $schedule) + ->setAttribute('updated', \time()) + ->setAttribute('next', $next) + ->setAttribute('security', $security) + ->setAttribute('httpMethod', $httpMethod) + ->setAttribute('httpUrl', $httpUrl) + ->setAttribute('httpHeaders', $httpHeaders) + ->setAttribute('httpUser', $httpUser) + ->setAttribute('httpPass', $httpPass) + , 'tasks'); $dbForConsole->updateDocument('projects', $project->getId(), $project); @@ -1227,13 +1226,13 @@ App::post('/v1/projects/:projectId/platforms') ]); $project = $dbForConsole->updateDocument('projects', $project->getId(), $project - ->setAttribute('platforms', $platform, Document::SET_TYPE_APPEND) + ->setAttribute('platforms', $platform, Document::SET_TYPE_APPEND) ); $response->setStatusCode(Response::STATUS_CODE_CREATED); $response->dynamic2($platform, Response::MODEL_PLATFORM); }); - + App::get('/v1/projects/:projectId/platforms') ->desc('List Platforms') ->groups(['api', 'projects']) @@ -1341,12 +1340,12 @@ App::put('/v1/projects/:projectId/platforms/:platformId') ; $project->findAndReplace('$id', $platform->getId(), $platform - ->setAttribute('name', $name) - ->setAttribute('dateUpdated', \time()) - ->setAttribute('key', $key) - ->setAttribute('store', $store) - ->setAttribute('hostname', $hostname) - , 'platforms'); + ->setAttribute('name', $name) + ->setAttribute('dateUpdated', \time()) + ->setAttribute('key', $key) + ->setAttribute('store', $store) + ->setAttribute('hostname', $hostname) + , 'platforms'); $dbForConsole->updateDocument('projects', $project->getId(), $project); @@ -1420,7 +1419,7 @@ App::post('/v1/projects/:projectId/domains') $target = new Domain(App::getEnv('_APP_DOMAIN_TARGET', '')); if (!$target->isKnown() || $target->isTest()) { - throw new Exception('Unreachable CNAME target ('.$target->get().'), please use a domain with a public suffix.', 500); + throw new Exception('Unreachable CNAME target (' . $target->get() . '), please use a domain with a public suffix.', 500); } $domain = new Domain($domain); @@ -1436,7 +1435,7 @@ App::post('/v1/projects/:projectId/domains') ]); $project = $dbForConsole->updateDocument('projects', $project->getId(), $project - ->setAttribute('domains', $domain, Document::SET_TYPE_APPEND) + ->setAttribute('domains', $domain, Document::SET_TYPE_APPEND) ); $response->setStatusCode(Response::STATUS_CODE_CREATED); @@ -1467,7 +1466,7 @@ App::get('/v1/projects/:projectId/domains') } $domains = $project->getAttribute('domains', []); - + $response->dynamic2(new Document([ 'domains' => $domains, 'sum' => count($domains), @@ -1540,7 +1539,7 @@ App::patch('/v1/projects/:projectId/domains/:domainId/verification') $target = new Domain(App::getEnv('_APP_DOMAIN_TARGET', '')); if (!$target->isKnown() || $target->isTest()) { - throw new Exception('Unreachable CNAME target ('.$target->get().'), please use a domain with a public suffix.', 500); + throw new Exception('Unreachable CNAME target (' . $target->get() . '), please use a domain with a public suffix.', 500); } if ($domain->getAttribute('verification') === true) { @@ -1554,8 +1553,8 @@ App::patch('/v1/projects/:projectId/domains/:domainId/verification') } $project->findAndReplace('$id', $domain->getId(), $domain - ->setAttribute('verification', true) - , 'domains'); + ->setAttribute('verification', true) + , 'domains'); $dbForConsole->updateDocument('projects', $project->getId(), $project); @@ -1606,4 +1605,4 @@ App::delete('/v1/projects/:projectId/domains/:domainId') ; $response->noContent(); - }); \ No newline at end of file + }); diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 618299eed9..355bfd9c50 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -13,7 +13,6 @@ use Utopia\Validator\Text; use Utopia\Validator\Range; use Utopia\Validator\Boolean; use Utopia\Audit\Audit; -use Utopia\Audit\Adapters\MySQL as AuditAdapter; use Utopia\Database\Document; use Utopia\Database\Exception\Duplicate; use Utopia\Database\Validator\UID; @@ -212,12 +211,10 @@ App::get('/v1/users/:userId/logs') ->label('sdk.response.model', Response::MODEL_LOG_LIST) ->param('userId', '', new UID(), 'User unique ID.') ->inject('response') - ->inject('register') - ->inject('project') ->inject('dbForInternal') ->inject('locale') ->inject('geodb') - ->action(function ($userId, $response, $register, $project, $dbForInternal, $locale, $geodb) { + ->action(function ($userId, $response, $dbForInternal, $locale, $geodb) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Registry\Registry $register */ /** @var Appwrite\Database\Document $project */ @@ -231,10 +228,7 @@ App::get('/v1/users/:userId/logs') throw new Exception('User not found', 404); } - $adapter = new AuditAdapter($register->get('db')); - $adapter->setNamespace('app_'.$project->getId()); - - $audit = new Audit($adapter); + $audit = new Audit($dbForInternal); $countries = $locale->getText('countries'); diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index 18aaea5b78..cb0a9e2e42 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -9,7 +9,7 @@ use Utopia\Abuse\Adapters\TimeLimit; use Utopia\Storage\Device\Local; use Utopia\Storage\Storage; -App::init(function ($utopia, $request, $response, $project, $user, $register, $events, $audits, $usage, $deletes) { +App::init(function ($utopia, $request, $response, $project, $user, $register, $events, $audits, $usage, $deletes, $dbForInternal) { /** @var Utopia\App $utopia */ /** @var Utopia\Swoole\Request $request */ /** @var Appwrite\Utopia\Response $response */ @@ -21,6 +21,7 @@ App::init(function ($utopia, $request, $response, $project, $user, $register, $e /** @var Appwrite\Event\Event $usage */ /** @var Appwrite\Event\Event $deletes */ /** @var Appwrite\Event\Event $functions */ + /** @var Utopia\Database\Database $dbForInternal */ Storage::setDevice('files', new Local(APP_STORAGE_UPLOADS.'/app-'.$project->getId())); Storage::setDevice('functions', new Local(APP_STORAGE_FUNCTIONS.'/app-'.$project->getId())); @@ -31,47 +32,44 @@ App::init(function ($utopia, $request, $response, $project, $user, $register, $e throw new Exception('Missing or unknown project ID', 400); } - // /* - // * Abuse Check - // */ - // $timeLimit = new TimeLimit($route->getLabel('abuse-key', 'url:{url},ip:{ip}'), $route->getLabel('abuse-limit', 0), $route->getLabel('abuse-time', 3600), function () use ($register) { - // return $register->get('db'); - // }); - // $timeLimit->setNamespace('app_'.$project->getId()); - // $timeLimit - // ->setParam('{userId}', $user->getId()) - // ->setParam('{userAgent}', $request->getUserAgent('')) - // ->setParam('{ip}', $request->getIP()) - // ->setParam('{url}', $request->getHostname().$route->getURL()) - // ; + /* + * Abuse Check + */ + $timeLimit = new TimeLimit($route->getLabel('abuse-key', 'url:{url},ip:{ip}'), $route->getLabel('abuse-limit', 0), $route->getLabel('abuse-time', 3600), $dbForInternal); + $timeLimit + ->setParam('{userId}', $user->getId()) + ->setParam('{userAgent}', $request->getUserAgent('')) + ->setParam('{ip}', $request->getIP()) + ->setParam('{url}', $request->getHostname().$route->getURL()) + ; - // //TODO make sure we get array here + //TODO make sure we get array here - // foreach ($request->getParams() as $key => $value) { // Set request params as potential abuse keys - // if(!empty($value)) { - // $timeLimit->setParam('{param-'.$key.'}', (\is_array($value)) ? \json_encode($value) : $value); - // } - // } + foreach ($request->getParams() as $key => $value) { // Set request params as potential abuse keys + if(!empty($value)) { + $timeLimit->setParam('{param-'.$key.'}', (\is_array($value)) ? \json_encode($value) : $value); + } + } - // $abuse = new Abuse($timeLimit); + $abuse = new Abuse($timeLimit); - // if ($timeLimit->limit()) { - // $response - // ->addHeader('X-RateLimit-Limit', $timeLimit->limit()) - // ->addHeader('X-RateLimit-Remaining', $timeLimit->remaining()) - // ->addHeader('X-RateLimit-Reset', $timeLimit->time() + $route->getLabel('abuse-time', 3600)) - // ; - // } + if ($timeLimit->limit()) { + $response + ->addHeader('X-RateLimit-Limit', $timeLimit->limit()) + ->addHeader('X-RateLimit-Remaining', $timeLimit->remaining()) + ->addHeader('X-RateLimit-Reset', $timeLimit->time() + $route->getLabel('abuse-time', 3600)) + ; + } - // $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::$roles); - // $isAppUser = Auth::isAppUser(Authorization::$roles); + $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::$roles); + $isAppUser = Auth::isAppUser(Authorization::$roles); - // if (($abuse->check() // Route is rate-limited - // && App::getEnv('_APP_OPTIONS_ABUSE', 'enabled') !== 'disabled') // Abuse is not diabled - // && (!$isAppUser && !$isPrivilegedUser)) // User is not an admin or API key - // { - // throw new Exception('Too many requests', 429); - // } + if (($abuse->check() // Route is rate-limited + && App::getEnv('_APP_OPTIONS_ABUSE', 'enabled') !== 'disabled') // Abuse is not diabled + && (!$isAppUser && !$isPrivilegedUser)) // User is not an admin or API key + { + throw new Exception('Too many requests', 429); + } /* * Background Jobs @@ -111,7 +109,7 @@ App::init(function ($utopia, $request, $response, $project, $user, $register, $e ->setParam('projectId', $project->getId()) ; -}, ['utopia', 'request', 'response', 'project', 'user', 'register', 'events', 'audits', 'usage', 'deletes'], 'api'); +}, ['utopia', 'request', 'response', 'project', 'user', 'register', 'events', 'audits', 'usage', 'deletes', 'dbForInternal'], 'api'); App::init(function ($utopia, $request, $response, $project, $user) { diff --git a/app/workers/audits.php b/app/workers/audits.php index d6027dd2e3..237b44258b 100644 --- a/app/workers/audits.php +++ b/app/workers/audits.php @@ -2,7 +2,11 @@ use Utopia\Audit\Audit; use Utopia\Audit\Adapters\MySQL as AuditAdapter; +use Utopia\Cache\Adapter\Redis; +use Utopia\Cache\Cache; use Utopia\CLI\Console; +use Utopia\Database\Adapter\MariaDB; +use Utopia\Database\Database; require_once __DIR__.'/../init.php'; @@ -31,10 +35,11 @@ class AuditsV1 $data = $this->args['data']; $db = $register->get('db', true); - $adapter = new AuditAdapter($db); - $adapter->setNamespace('app_'.$projectId); + $cache = new Cache(new Redis($register->get('cache'))); + $dbForInternal = new Database(new MariaDB($db), $cache); + $dbForInternal->setNamespace('project_'.$projectId.'_internal'); - $audit = new Audit($adapter); + $audit = new Audit($dbForInternal); $audit->log($userId, $event, $resource, $userAgent, $ip, '', $data); } diff --git a/app/workers/deletes.php b/app/workers/deletes.php index 5316783c49..b83a7c4c23 100644 --- a/app/workers/deletes.php +++ b/app/workers/deletes.php @@ -1,6 +1,8 @@ get('db'); - }); - - $this->deleteForProjectIds(function($projectId) use ($timeLimit, $timestamp){ - $timeLimit->setNamespace('app_'.$projectId); + + $this->deleteForProjectIds(function($projectId) use ($timestamp){ + $timeLimit = new TimeLimit("", 0, 1, $this->getInternalDB($projectId)); $abuse = new Abuse($timeLimit); $status = $abuse->cleanup($timestamp); @@ -189,9 +190,7 @@ class DeletesV1 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); + $audit = new Audit($this->getInternalDB($projectId)); $status = $audit->cleanup($timestamp); if (!$status) { throw new Exception('Failed to delete Audit logs for project'.$projectId); @@ -375,4 +374,18 @@ class DeletesV1 return $projectDB; } + + /** + * @return Database2 + */ + protected function getInternalDB($projectId): Database2 + { + global $register; + + $cache = new Cache(new RedisCache($register->get('cache'))); + $dbForInternal = new Database2(new MariaDB($register->get('db')), $cache); + $dbForInternal->setNamespace('project_'.$projectId.'_internal'); // Main DB + + return $dbForInternal; + } } \ No newline at end of file diff --git a/composer.json b/composer.json index c5cf7a271d..7b30b3209e 100644 --- a/composer.json +++ b/composer.json @@ -39,9 +39,9 @@ "appwrite/php-runtimes": "0.2.*", "utopia-php/framework": "0.14.*", - "utopia-php/abuse": "0.4.*", + "utopia-php/abuse": "dev-feat-utopia-db-integration", "utopia-php/analytics": "0.2.*", - "utopia-php/audit": "0.5.*", + "utopia-php/audit": "dev-feat-utopia-db-integration", "utopia-php/cache": "0.4.*", "utopia-php/cli": "0.11.*", "utopia-php/config": "0.2.*", @@ -62,6 +62,16 @@ "adhocore/jwt": "1.1.2", "slickdeals/statsd": "3.0.2" }, + "repositories": [ + { + "type": "git", + "url": "https://github.com/lohanidamodar/audit" + }, + { + "type": "git", + "url": "https://github.com/lohanidamodar/abuse" + } + ], "require-dev": { "appwrite/sdk-generator": "0.10.6", "swoole/ide-helper": "4.6.6", diff --git a/composer.lock b/composer.lock index b2d89a1e6a..76df100aff 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "5cf39daf305902a168233757d4a00066", + "content-hash": "3eb7dc73e9524209a38bd6c33a540732", "packages": [ { "name": "adhocore/jwt", @@ -1603,21 +1603,22 @@ }, { "name": "utopia-php/abuse", - "version": "0.4.0", + "version": "dev-feat-utopia-db-integration", "source": { "type": "git", - "url": "https://github.com/utopia-php/abuse.git", - "reference": "2b8cc40a67c045c137b44d1a11326f494acf50a4" + "url": "https://github.com/lohanidamodar/abuse.git", + "reference": "525cdf6674e11a7229cba31941915ac50a3e575a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/abuse/zipball/2b8cc40a67c045c137b44d1a11326f494acf50a4", - "reference": "2b8cc40a67c045c137b44d1a11326f494acf50a4", + "url": "https://api.github.com/repos/lohanidamodar/abuse/zipball/525cdf6674e11a7229cba31941915ac50a3e575a", + "reference": "525cdf6674e11a7229cba31941915ac50a3e575a", "shasum": "" }, "require": { "ext-pdo": "*", - "php": ">=7.4" + "php": ">=7.4", + "utopia-php/database": "0.2.*" }, "require-dev": { "phpunit/phpunit": "^9.4", @@ -1629,7 +1630,6 @@ "Utopia\\Abuse\\": "src/Abuse" } }, - "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], @@ -1641,17 +1641,16 @@ ], "description": "A simple abuse library to manage application usage limits", "keywords": [ - "Abuse", + "abuse", "framework", "php", "upf", "utopia" ], "support": { - "issues": "https://github.com/utopia-php/abuse/issues", - "source": "https://github.com/utopia-php/abuse/tree/0.4.0" + "source": "https://github.com/lohanidamodar/abuse/tree/feat-utopia-db-integration" }, - "time": "2021-03-17T20:21:24+00:00" + "time": "2021-06-04T08:40:29+00:00" }, { "name": "utopia-php/analytics", @@ -1710,21 +1709,22 @@ }, { "name": "utopia-php/audit", - "version": "0.5.1", + "version": "dev-feat-utopia-db-integration", "source": { "type": "git", - "url": "https://github.com/utopia-php/audit.git", - "reference": "154a850170a58667a15e4b65fbabb6cd0b709dd9" + "url": "https://github.com/lohanidamodar/audit.git", + "reference": "3421b6a54740fbb29570004848cdc0b818717e83" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/audit/zipball/154a850170a58667a15e4b65fbabb6cd0b709dd9", - "reference": "154a850170a58667a15e4b65fbabb6cd0b709dd9", + "url": "https://api.github.com/repos/lohanidamodar/audit/zipball/3421b6a54740fbb29570004848cdc0b818717e83", + "reference": "3421b6a54740fbb29570004848cdc0b818717e83", "shasum": "" }, "require": { "ext-pdo": "*", - "php": ">=7.1" + "php": ">=7.4", + "utopia-php/database": "0.2.*" }, "require-dev": { "phpunit/phpunit": "^9.3", @@ -1736,7 +1736,6 @@ "Utopia\\Audit\\": "src/Audit" } }, - "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], @@ -1748,17 +1747,16 @@ ], "description": "A simple audit library to manage application users logs", "keywords": [ - "Audit", + "audit", "framework", "php", "upf", "utopia" ], "support": { - "issues": "https://github.com/utopia-php/audit/issues", - "source": "https://github.com/utopia-php/audit/tree/0.5.1" + "source": "https://github.com/lohanidamodar/audit/tree/feat-utopia-db-integration" }, - "time": "2020-12-21T17:28:53+00:00" + "time": "2021-06-04T08:41:19+00:00" }, { "name": "utopia-php/cache", @@ -5252,20 +5250,21 @@ }, { "name": "symfony/console", - "version": "v5.2.8", + "version": "v5.3.0", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "864568fdc0208b3eba3638b6000b69d2386e6768" + "reference": "058553870f7809087fa80fa734704a21b9bcaeb2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/864568fdc0208b3eba3638b6000b69d2386e6768", - "reference": "864568fdc0208b3eba3638b6000b69d2386e6768", + "url": "https://api.github.com/repos/symfony/console/zipball/058553870f7809087fa80fa734704a21b9bcaeb2", + "reference": "058553870f7809087fa80fa734704a21b9bcaeb2", "shasum": "" }, "require": { "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1", "symfony/polyfill-mbstring": "~1.0", "symfony/polyfill-php73": "^1.8", "symfony/polyfill-php80": "^1.15", @@ -5329,7 +5328,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.2.8" + "source": "https://github.com/symfony/console/tree/v5.3.0" }, "funding": [ { @@ -5345,7 +5344,74 @@ "type": "tidelift" } ], - "time": "2021-05-11T15:45:21+00:00" + "time": "2021-05-26T17:43:10+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v2.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "5f38c8804a9e97d23e0c8d63341088cd8a22d627" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/5f38c8804a9e97d23e0c8d63341088cd8a22d627", + "reference": "5f38c8804a9e97d23e0c8d63341088cd8a22d627", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.4-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v2.4.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-03-23T23:28:01+00:00" }, { "name": "symfony/polyfill-intl-grapheme", @@ -5752,16 +5818,16 @@ }, { "name": "symfony/string", - "version": "v5.2.8", + "version": "v5.3.0", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "01b35eb64cac8467c3f94cd0ce2d0d376bb7d1db" + "reference": "a9a0f8b6aafc5d2d1c116dcccd1573a95153515b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/01b35eb64cac8467c3f94cd0ce2d0d376bb7d1db", - "reference": "01b35eb64cac8467c3f94cd0ce2d0d376bb7d1db", + "url": "https://api.github.com/repos/symfony/string/zipball/a9a0f8b6aafc5d2d1c116dcccd1573a95153515b", + "reference": "a9a0f8b6aafc5d2d1c116dcccd1573a95153515b", "shasum": "" }, "require": { @@ -5815,7 +5881,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v5.2.8" + "source": "https://github.com/symfony/string/tree/v5.3.0" }, "funding": [ { @@ -5831,7 +5897,7 @@ "type": "tidelift" } ], - "time": "2021-05-10T14:56:10+00:00" + "time": "2021-05-26T17:43:10+00:00" }, { "name": "theseer/tokenizer", @@ -6120,7 +6186,10 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": { + "utopia-php/abuse": 20, + "utopia-php/audit": 20 + }, "prefer-stable": false, "prefer-lowest": false, "platform": { @@ -6143,4 +6212,4 @@ "php": "8.0" }, "plugin-api-version": "2.0.0" -} \ No newline at end of file +} From ee6798666267da32daaaf760ea0c6320006cc0bc Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 7 Jun 2021 11:37:40 +0545 Subject: [PATCH 40/60] db setup --- app/controllers/api/projects.php | 8 +++++++ composer.lock | 36 ++++++++------------------------ 2 files changed, 17 insertions(+), 27 deletions(-) diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index 6af20880a9..897eafa51e 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -20,6 +20,8 @@ use Utopia\Validator\Integer; use Utopia\Validator\Range; use Utopia\Validator\Text; use Utopia\Validator\WhiteList; +use Utopia\Audit\Audit; +use Utopia\Abuse\Adapters\TimeLimit; App::post('/v1/projects') ->desc('Create Project') @@ -92,6 +94,12 @@ App::post('/v1/projects') $dbForExternal->setNamespace('project_' . $project->getId() . '_external'); $dbForExternal->create(); + $audit = new Audit($dbForInternal); + $audit->setup(); + + $adapter = new TimeLimit("", 0, 1, $dbForInternal); + $adapter->setup(); + foreach ($collections as $key => $collection) { $dbForInternal->createCollection($key); diff --git a/composer.lock b/composer.lock index 76df100aff..cc7393c775 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "3eb7dc73e9524209a38bd6c33a540732", + "content-hash": "e3617b2fd699e599dc2c1d7ed74c9f44", "packages": [ { "name": "adhocore/jwt", @@ -1606,15 +1606,9 @@ "version": "dev-feat-utopia-db-integration", "source": { "type": "git", - "url": "https://github.com/lohanidamodar/abuse.git", + "url": "https://github.com/lohanidamodar/abuse", "reference": "525cdf6674e11a7229cba31941915ac50a3e575a" }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/lohanidamodar/abuse/zipball/525cdf6674e11a7229cba31941915ac50a3e575a", - "reference": "525cdf6674e11a7229cba31941915ac50a3e575a", - "shasum": "" - }, "require": { "ext-pdo": "*", "php": ">=7.4", @@ -1647,9 +1641,6 @@ "upf", "utopia" ], - "support": { - "source": "https://github.com/lohanidamodar/abuse/tree/feat-utopia-db-integration" - }, "time": "2021-06-04T08:40:29+00:00" }, { @@ -1712,15 +1703,9 @@ "version": "dev-feat-utopia-db-integration", "source": { "type": "git", - "url": "https://github.com/lohanidamodar/audit.git", + "url": "https://github.com/lohanidamodar/audit", "reference": "3421b6a54740fbb29570004848cdc0b818717e83" }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/lohanidamodar/audit/zipball/3421b6a54740fbb29570004848cdc0b818717e83", - "reference": "3421b6a54740fbb29570004848cdc0b818717e83", - "shasum": "" - }, "require": { "ext-pdo": "*", "php": ">=7.4", @@ -1753,9 +1738,6 @@ "upf", "utopia" ], - "support": { - "source": "https://github.com/lohanidamodar/audit/tree/feat-utopia-db-integration" - }, "time": "2021-06-04T08:41:19+00:00" }, { @@ -5089,16 +5071,16 @@ }, { "name": "sebastian/type", - "version": "2.3.1", + "version": "2.3.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "81cd61ab7bbf2de744aba0ea61fae32f721df3d2" + "reference": "0d1c587401514d17e8f9258a27e23527cb1b06c1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/81cd61ab7bbf2de744aba0ea61fae32f721df3d2", - "reference": "81cd61ab7bbf2de744aba0ea61fae32f721df3d2", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/0d1c587401514d17e8f9258a27e23527cb1b06c1", + "reference": "0d1c587401514d17e8f9258a27e23527cb1b06c1", "shasum": "" }, "require": { @@ -5133,7 +5115,7 @@ "homepage": "https://github.com/sebastianbergmann/type", "support": { "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/2.3.1" + "source": "https://github.com/sebastianbergmann/type/tree/2.3.2" }, "funding": [ { @@ -5141,7 +5123,7 @@ "type": "github" } ], - "time": "2020-10-26T13:18:59+00:00" + "time": "2021-06-04T13:02:07+00:00" }, { "name": "sebastian/version", From 9a9a309ce22231c554da8daace9fa28d2996c96f Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Wed, 9 Jun 2021 12:30:02 +0545 Subject: [PATCH 41/60] Update app/controllers/api/projects.php Co-authored-by: Eldad A. Fux --- app/controllers/api/projects.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index a58af2afe8..174b18f98d 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -97,7 +97,7 @@ App::post('/v1/projects') $audit = new Audit($dbForInternal); $audit->setup(); - $adapter = new TimeLimit("", 0, 1, $dbForInternal); + $adapter = new TimeLimit("", 0, 1, $dbForInternal); $adapter->setup(); foreach ($collections as $key => $collection) { From ffa06e7de45245a38dd37f542192fd0fa7981b37 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Wed, 9 Jun 2021 15:13:12 +0545 Subject: [PATCH 42/60] abuse audit setup for ocnsole db --- app/http.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/http.php b/app/http.php index 0856a69f9a..b4f70e190f 100644 --- a/app/http.php +++ b/app/http.php @@ -14,6 +14,8 @@ use Utopia\App; use Utopia\CLI\Console; use Utopia\Config\Config; use Utopia\Database\Validator\Authorization as Authorization2; +use Utopia\Audit\Audit; +use Utopia\Abuse\Adapters\TimeLimit; // xdebug_start_trace('/tmp/trace'); @@ -67,6 +69,12 @@ $http->on('start', function (Server $http) use ($payloadSize, $register) { $register->get('cache')->flushAll(); $dbForConsole->create(); + + $audit = new Audit($dbForConsole); + $audit->setup(); + + $adapter = new TimeLimit("", 0, 1, $dbForConsole); + $adapter->setup(); foreach ($collections as $key => $collection) { $dbForConsole->createCollection($key); From 0a5646482f22ea3d1084ef918dd80db09f42ac25 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Wed, 9 Jun 2021 15:13:24 +0545 Subject: [PATCH 43/60] abuse-audit update --- composer.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/composer.lock b/composer.lock index cc7393c775..5d0f64368b 100644 --- a/composer.lock +++ b/composer.lock @@ -1607,7 +1607,7 @@ "source": { "type": "git", "url": "https://github.com/lohanidamodar/abuse", - "reference": "525cdf6674e11a7229cba31941915ac50a3e575a" + "reference": "4f3349b3c1c85353708d13f7eb3c34c0762d4828" }, "require": { "ext-pdo": "*", @@ -1641,7 +1641,7 @@ "upf", "utopia" ], - "time": "2021-06-04T08:40:29+00:00" + "time": "2021-06-09T09:11:16+00:00" }, { "name": "utopia-php/analytics", @@ -1704,7 +1704,7 @@ "source": { "type": "git", "url": "https://github.com/lohanidamodar/audit", - "reference": "3421b6a54740fbb29570004848cdc0b818717e83" + "reference": "b3b85524717bbf52cfe71f01474c4012bfd4323a" }, "require": { "ext-pdo": "*", @@ -1738,7 +1738,7 @@ "upf", "utopia" ], - "time": "2021-06-04T08:41:19+00:00" + "time": "2021-06-09T09:10:27+00:00" }, { "name": "utopia-php/cache", From 2cf16881f16243095a2460b89224c11f6d71bd78 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Fri, 11 Jun 2021 21:23:16 +0300 Subject: [PATCH 44/60] Replaced * with role:all --- app/config/collections.php | 40 +++++++++---------- app/controllers/api/account.php | 6 +-- app/controllers/api/teams.php | 4 +- app/controllers/api/users.php | 8 ++-- app/http.php | 4 +- app/views/console/settings/index.phtml | 2 +- app/views/console/storage/index.phtml | 2 +- composer.json | 2 +- composer.lock | 28 +++++++------ docs/sdks/dart/EXAMPLES.md | 2 +- docs/sdks/flutter-dev/EXAMPLES.md | 2 +- docs/sdks/flutter/EXAMPLES.md | 2 +- src/Appwrite/Auth/Auth.php | 2 +- .../Database/Validator/Authorization.php | 2 +- src/Appwrite/Utopia/Response/Model/File.php | 2 +- tests/e2e/Services/Database/DatabaseBase.php | 14 +++---- .../Database/DatabaseCustomServerTest.php | 2 +- .../Functions/FunctionsCustomClientTest.php | 2 +- tests/e2e/Services/Storage/StorageBase.php | 8 ++-- tests/e2e/Services/Webhooks/WebhooksBase.php | 24 +++++------ .../Webhooks/WebhooksCustomServerTest.php | 12 +++--- .../Database/Validator/AuthorizationTest.php | 4 +- tests/unit/Utopia/Filters/V06Test.php | 12 +++--- 23 files changed, 94 insertions(+), 92 deletions(-) diff --git a/app/config/collections.php b/app/config/collections.php index 6789237c9c..cf803ed837 100644 --- a/app/config/collections.php +++ b/app/config/collections.php @@ -11,7 +11,7 @@ $collections = [ 'console' => [ '$id' => 'console', '$collection' => 'projects', - '$permissions' => ['read' => ['*']], + '$permissions' => ['read' => ['role:all']], 'name' => 'Appwrite', 'description' => 'Appwrite core engine', 'logo' => '', @@ -51,7 +51,7 @@ $collections = [ Database::SYSTEM_COLLECTION_COLLECTIONS => [ '$collection' => Database::SYSTEM_COLLECTION_COLLECTIONS, '$id' => Database::SYSTEM_COLLECTION_COLLECTIONS, - '$permissions' => ['read' => ['*']], + '$permissions' => ['read' => ['role:all']], 'name' => 'Collections', 'structure' => true, 'rules' => [ @@ -106,7 +106,7 @@ $collections = [ Database::SYSTEM_COLLECTION_RULES => [ '$collection' => Database::SYSTEM_COLLECTION_COLLECTIONS, '$id' => Database::SYSTEM_COLLECTION_RULES, - '$permissions' => ['read' => ['*']], + '$permissions' => ['read' => ['role:all']], 'name' => 'Collections Rule', 'structure' => true, 'rules' => [ @@ -186,7 +186,7 @@ $collections = [ Database::SYSTEM_COLLECTION_USERS => [ '$collection' => Database::SYSTEM_COLLECTION_COLLECTIONS, '$id' => Database::SYSTEM_COLLECTION_USERS, - '$permissions' => ['read' => ['*']], + '$permissions' => ['read' => ['role:all']], 'name' => 'User', 'structure' => true, 'rules' => [ @@ -307,7 +307,7 @@ $collections = [ Database::SYSTEM_COLLECTION_SESSIONS => [ '$collection' => Database::SYSTEM_COLLECTION_COLLECTIONS, '$id' => Database::SYSTEM_COLLECTION_SESSIONS, - '$permissions' => ['read' => ['*']], + '$permissions' => ['read' => ['role:all']], 'name' => 'Session', 'structure' => true, 'rules' => [ @@ -505,7 +505,7 @@ $collections = [ Database::SYSTEM_COLLECTION_TOKENS => [ '$collection' => Database::SYSTEM_COLLECTION_COLLECTIONS, '$id' => Database::SYSTEM_COLLECTION_TOKENS, - '$permissions' => ['read' => ['*']], + '$permissions' => ['read' => ['role:all']], 'name' => 'Token', 'structure' => true, 'rules' => [ @@ -568,7 +568,7 @@ $collections = [ Database::SYSTEM_COLLECTION_MEMBERSHIPS => [ '$collection' => Database::SYSTEM_COLLECTION_COLLECTIONS, '$id' => Database::SYSTEM_COLLECTION_MEMBERSHIPS, - '$permissions' => ['read' => ['*']], + '$permissions' => ['read' => ['role:all']], 'name' => 'Membership', 'structure' => true, 'rules' => [ @@ -640,7 +640,7 @@ $collections = [ Database::SYSTEM_COLLECTION_TEAMS => [ '$collection' => Database::SYSTEM_COLLECTION_COLLECTIONS, '$id' => Database::SYSTEM_COLLECTION_TEAMS, - '$permissions' => ['read' => ['*']], + '$permissions' => ['read' => ['role:all']], 'name' => 'Team', 'structure' => true, 'rules' => [ @@ -676,7 +676,7 @@ $collections = [ Database::SYSTEM_COLLECTION_PROJECTS => [ '$collection' => Database::SYSTEM_COLLECTION_COLLECTIONS, '$id' => Database::SYSTEM_COLLECTION_PROJECTS, - '$permissions' => ['read' => ['*']], + '$permissions' => ['read' => ['role:all']], 'name' => 'Project', 'structure' => true, 'rules' => [ @@ -834,7 +834,7 @@ $collections = [ Database::SYSTEM_COLLECTION_WEBHOOKS => [ '$collection' => Database::SYSTEM_COLLECTION_COLLECTIONS, '$id' => Database::SYSTEM_COLLECTION_WEBHOOKS, - '$permissions' => ['read' => ['*']], + '$permissions' => ['read' => ['role:all']], 'name' => 'Webhook', 'structure' => true, 'rules' => [ @@ -898,7 +898,7 @@ $collections = [ Database::SYSTEM_COLLECTION_KEYS => [ '$collection' => Database::SYSTEM_COLLECTION_COLLECTIONS, '$id' => Database::SYSTEM_COLLECTION_KEYS, - '$permissions' => ['read' => ['*']], + '$permissions' => ['read' => ['role:all']], 'name' => 'Key', 'structure' => true, 'rules' => [ @@ -934,7 +934,7 @@ $collections = [ Database::SYSTEM_COLLECTION_TASKS => [ '$collection' => Database::SYSTEM_COLLECTION_COLLECTIONS, '$id' => Database::SYSTEM_COLLECTION_TASKS, - '$permissions' => ['read' => ['*']], + '$permissions' => ['read' => ['role:all']], 'name' => 'Task', 'structure' => true, 'rules' => [ @@ -1088,7 +1088,7 @@ $collections = [ Database::SYSTEM_COLLECTION_PLATFORMS => [ '$collection' => Database::SYSTEM_COLLECTION_COLLECTIONS, '$id' => Database::SYSTEM_COLLECTION_PLATFORMS, - '$permissions' => ['read' => ['*']], + '$permissions' => ['read' => ['role:all']], 'name' => 'Platform', 'structure' => true, 'rules' => [ @@ -1160,7 +1160,7 @@ $collections = [ Database::SYSTEM_COLLECTION_DOMAINS => [ '$collection' => Database::SYSTEM_COLLECTION_COLLECTIONS, '$id' => Database::SYSTEM_COLLECTION_DOMAINS, - '$permissions' => ['read' => ['*']], + '$permissions' => ['read' => ['role:all']], 'name' => 'Domains', 'structure' => true, 'rules' => [ @@ -1223,7 +1223,7 @@ $collections = [ Database::SYSTEM_COLLECTION_CERTIFICATES => [ '$collection' => Database::SYSTEM_COLLECTION_COLLECTIONS, '$id' => Database::SYSTEM_COLLECTION_CERTIFICATES, - '$permissions' => ['read' => ['*']], + '$permissions' => ['read' => ['role:all']], 'name' => 'Certificates', 'structure' => true, 'rules' => [ @@ -1295,7 +1295,7 @@ $collections = [ Database::SYSTEM_COLLECTION_FILES => [ '$collection' => Database::SYSTEM_COLLECTION_COLLECTIONS, '$id' => Database::SYSTEM_COLLECTION_FILES, - '$permissions' => ['read' => ['*']], + '$permissions' => ['read' => ['role:all']], 'name' => 'File', 'structure' => true, 'rules' => [ @@ -1439,7 +1439,7 @@ $collections = [ Database::SYSTEM_COLLECTION_FUNCTIONS => [ '$collection' => Database::SYSTEM_COLLECTION_COLLECTIONS, '$id' => Database::SYSTEM_COLLECTION_FUNCTIONS, - '$permissions' => ['read' => ['*']], + '$permissions' => ['read' => ['role:all']], 'name' => 'Functions', 'structure' => true, 'rules' => [ @@ -1557,7 +1557,7 @@ $collections = [ Database::SYSTEM_COLLECTION_TAGS => [ '$collection' => Database::SYSTEM_COLLECTION_COLLECTIONS, '$id' => Database::SYSTEM_COLLECTION_TAGS, - '$permissions' => ['read' => ['*']], + '$permissions' => ['read' => ['role:all']], 'name' => 'Tags', 'structure' => true, 'rules' => [ @@ -1611,7 +1611,7 @@ $collections = [ Database::SYSTEM_COLLECTION_EXECUTIONS => [ '$collection' => Database::SYSTEM_COLLECTION_COLLECTIONS, '$id' => Database::SYSTEM_COLLECTION_EXECUTIONS, - '$permissions' => ['read' => ['*']], + '$permissions' => ['read' => ['role:all']], 'name' => 'Executions', 'structure' => true, 'rules' => [ @@ -1701,7 +1701,7 @@ $collections = [ Database::SYSTEM_COLLECTION_RESERVED => [ '$collection' => Database::SYSTEM_COLLECTION_COLLECTIONS, '$id' => Database::SYSTEM_COLLECTION_RESERVED, - '$permissions' => ['read' => ['*']], + '$permissions' => ['read' => ['role:all']], 'name' => 'Reserved', 'structure' => true, ], diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index a10c940357..6a3799416c 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -88,7 +88,7 @@ App::post('/v1/account') $userId = $dbForInternal->getId(); $user = $dbForInternal->createDocument('users', new Document([ '$id' => $userId, - '$read' => ['*'], + '$read' => ['role:all'], '$write' => ['user:'.$userId], 'email' => $email, 'emailVerification' => false, @@ -464,7 +464,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') $userId = $dbForInternal->getId(); $user = $dbForInternal->createDocument('users', new Document([ '$id' => $userId, - '$read' => ['*'], + '$read' => ['role:all'], '$write' => ['user:'.$userId], 'email' => $email, 'emailVerification' => true, @@ -621,7 +621,7 @@ App::post('/v1/account/sessions/anonymous') $userId = $dbForInternal->getId(); $user = $dbForInternal->createDocument('users', new Document([ '$id' => $userId, - '$read' => ['*'], + '$read' => ['role:all'], '$write' => ['user:'.$userId], 'email' => null, 'emailVerification' => false, diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index 682bbd642e..588651f705 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -286,7 +286,7 @@ App::post('/v1/teams/:teamId/memberships') $userId = $dbForInternal->getId(); $invitee = $dbForInternal->createDocument('users', new Document([ '$id' => $userId, - '$read' => ['user:'.$userId, '*'], + '$read' => ['user:'.$userId, 'role:all'], '$write' => ['user:'.$userId], 'email' => $email, 'emailVerification' => false, @@ -323,7 +323,7 @@ App::post('/v1/teams/:teamId/memberships') $membership = new Document([ '$id' => $dbForInternal->getId(), - '$read' => ['*'], + '$read' => ['role:all'], '$write' => ['user:'.$invitee->getId(), 'team:'.$team->getId().'/owner'], 'userId' => $invitee->getId(), 'teamId' => $team->getId(), diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index fcd6aaee79..b1fb0aaf5a 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -46,7 +46,7 @@ App::post('/v1/users') $userId = $dbForInternal->getId(); $user = $dbForInternal->createDocument('users', new Document([ '$id' => $userId, - '$read' => ['*'], + '$read' => ['role:all'], '$write' => ['user:'.$userId], 'email' => $email, 'emailVerification' => false, @@ -361,8 +361,8 @@ App::patch('/v1/users/:userId/verification') ->param('userId', '', new UID(), 'User unique ID.') ->param('emailVerification', false, new Boolean(), 'User Email Verification Status.') ->inject('response') - ->inject('projectDB') - ->action(function ($userId, $emailVerification, $response, $projectDB) { + ->inject('dbForInternal') + ->action(function ($userId, $emailVerification, $response, $dbForInternal) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForInternal */ @@ -527,7 +527,7 @@ App::delete('/v1/users/:userId') // $dbForInternal->createDocument('users', new Document([ // '$id' => $userId, - // '$read' => ['*'], + // '$read' => ['role:all'], // ])); $deletes diff --git a/app/http.php b/app/http.php index 0856a69f9a..246896477b 100644 --- a/app/http.php +++ b/app/http.php @@ -131,10 +131,10 @@ $http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swo try { Authorization::cleanRoles(); - Authorization::setRole('*'); + Authorization::setRole('role:all'); Authorization2::cleanRoles(); - Authorization2::setRole('*'); + Authorization2::setRole('role:all'); $app->run($request, $response); } catch (\Throwable $th) { diff --git a/app/views/console/settings/index.phtml b/app/views/console/settings/index.phtml index 8af3d6e53f..66c3e3dc56 100644 --- a/app/views/console/settings/index.phtml +++ b/app/views/console/settings/index.phtml @@ -58,7 +58,7 @@ $smtpEnabled = $this->getParam('smtpEnabled', false);
- +

diff --git a/app/views/console/storage/index.phtml b/app/views/console/storage/index.phtml index 95ae7c925e..7eba230608 100644 --- a/app/views/console/storage/index.phtml +++ b/app/views/console/storage/index.phtml @@ -243,7 +243,7 @@ $fileLimitHuman = $this->getParam('fileLimitHuman', 0);
(Max file size allowed: )
- +
Add * for wildcard access
diff --git a/composer.json b/composer.json index bffcaa533c..bbfb4af082 100644 --- a/composer.json +++ b/composer.json @@ -45,7 +45,7 @@ "utopia-php/cache": "0.4.*", "utopia-php/cli": "0.11.*", "utopia-php/config": "0.2.*", - "utopia-php/database": "0.2.*", + "utopia-php/database": "0.3.*", "utopia-php/locale": "0.3.*", "utopia-php/registry": "0.4.*", "utopia-php/preloader": "0.2.*", diff --git a/composer.lock b/composer.lock index 6249f773bf..1075866385 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "271f550b529e2380be5501863def0560", + "content-hash": "0456f223731164e0d43456a74949d8bf", "packages": [ { "name": "adhocore/jwt", @@ -1919,16 +1919,16 @@ }, { "name": "utopia-php/database", - "version": "0.2.0", + "version": "0.3.1", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "b5dd144d582f3355c13f5430b1b3d7eb850bc5cd" + "reference": "6f8b7184ae4971672188607ea311a22a50da6767" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/b5dd144d582f3355c13f5430b1b3d7eb850bc5cd", - "reference": "b5dd144d582f3355c13f5430b1b3d7eb850bc5cd", + "url": "https://api.github.com/repos/utopia-php/database/zipball/6f8b7184ae4971672188607ea311a22a50da6767", + "reference": "6f8b7184ae4971672188607ea311a22a50da6767", "shasum": "" }, "require": { @@ -1941,7 +1941,9 @@ "utopia-php/framework": "0.*.*" }, "require-dev": { + "fakerphp/faker": "^1.14", "phpunit/phpunit": "^9.4", + "utopia-php/cli": "^0.11.0", "vimeo/psalm": "4.0.1" }, "type": "library", @@ -1974,9 +1976,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.2.0" + "source": "https://github.com/utopia-php/database/tree/0.3.1" }, - "time": "2021-05-26T18:41:44+00:00" + "time": "2021-06-11T15:02:46+00:00" }, { "name": "utopia-php/domains", @@ -4740,16 +4742,16 @@ }, { "name": "sebastian/global-state", - "version": "5.0.2", + "version": "5.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "a90ccbddffa067b51f574dea6eb25d5680839455" + "reference": "23bd5951f7ff26f12d4e3242864df3e08dec4e49" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/a90ccbddffa067b51f574dea6eb25d5680839455", - "reference": "a90ccbddffa067b51f574dea6eb25d5680839455", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/23bd5951f7ff26f12d4e3242864df3e08dec4e49", + "reference": "23bd5951f7ff26f12d4e3242864df3e08dec4e49", "shasum": "" }, "require": { @@ -4792,7 +4794,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", - "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.2" + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.3" }, "funding": [ { @@ -4800,7 +4802,7 @@ "type": "github" } ], - "time": "2020-10-26T15:55:19+00:00" + "time": "2021-06-11T13:31:12+00:00" }, { "name": "sebastian/lines-of-code", diff --git a/docs/sdks/dart/EXAMPLES.md b/docs/sdks/dart/EXAMPLES.md index 26930c3352..c99d3ad617 100644 --- a/docs/sdks/dart/EXAMPLES.md +++ b/docs/sdks/dart/EXAMPLES.md @@ -44,7 +44,7 @@ MultipartFile file = MultipartFile.fromFile('./path-to-file/image.jpg', filename storage.createFile( file: file, - read: ['*'], + read: ['role:all'], write: [] ) .then((response) { diff --git a/docs/sdks/flutter-dev/EXAMPLES.md b/docs/sdks/flutter-dev/EXAMPLES.md index f6768a5f85..ea92ec6e8e 100644 --- a/docs/sdks/flutter-dev/EXAMPLES.md +++ b/docs/sdks/flutter-dev/EXAMPLES.md @@ -41,7 +41,7 @@ MultipartFile file = MultipartFile.fromFile('./path-to-file/image.jpg', filename storage.createFile( file: file, - read: ['*'], + read: ['role:all'], write: [] ) .then((response) { diff --git a/docs/sdks/flutter/EXAMPLES.md b/docs/sdks/flutter/EXAMPLES.md index f6768a5f85..ea92ec6e8e 100644 --- a/docs/sdks/flutter/EXAMPLES.md +++ b/docs/sdks/flutter/EXAMPLES.md @@ -41,7 +41,7 @@ MultipartFile file = MultipartFile.fromFile('./path-to-file/image.jpg', filename storage.createFile( file: file, - read: ['*'], + read: ['role:all'], write: [] ) .then((response) { diff --git a/src/Appwrite/Auth/Auth.php b/src/Appwrite/Auth/Auth.php index 9fa181486e..b25cfcdadf 100644 --- a/src/Appwrite/Auth/Auth.php +++ b/src/Appwrite/Auth/Auth.php @@ -16,6 +16,7 @@ class Auth /** * User Roles. */ + const USER_ROLE_ALL = 'all'; const USER_ROLE_GUEST = 'guest'; const USER_ROLE_MEMBER = 'member'; const USER_ROLE_ADMIN = 'admin'; @@ -23,7 +24,6 @@ class Auth const USER_ROLE_OWNER = 'owner'; const USER_ROLE_APP = 'app'; const USER_ROLE_SYSTEM = 'system'; - const USER_ROLE_ALL = '*'; /** * Token Types. diff --git a/src/Appwrite/Database/Validator/Authorization.php b/src/Appwrite/Database/Validator/Authorization.php index 1ae2554001..7e4a8e93fc 100644 --- a/src/Appwrite/Database/Validator/Authorization.php +++ b/src/Appwrite/Database/Validator/Authorization.php @@ -10,7 +10,7 @@ class Authorization extends Validator /** * @var array */ - static $roles = ['*' => true]; + static $roles = ['role:all' => true]; /** * @var Document diff --git a/src/Appwrite/Utopia/Response/Model/File.php b/src/Appwrite/Utopia/Response/Model/File.php index 15b1124dd6..65ac050781 100644 --- a/src/Appwrite/Utopia/Response/Model/File.php +++ b/src/Appwrite/Utopia/Response/Model/File.php @@ -20,7 +20,7 @@ class File extends Model 'type' => self::TYPE_STRING, 'description' => 'File read permissions.', 'default' => [], - 'example' => ['*'], + 'example' => ['role:all'], 'array' => true, ]) ->addRule('$write', [ diff --git a/tests/e2e/Services/Database/DatabaseBase.php b/tests/e2e/Services/Database/DatabaseBase.php index 6627018307..d018f29a8d 100644 --- a/tests/e2e/Services/Database/DatabaseBase.php +++ b/tests/e2e/Services/Database/DatabaseBase.php @@ -17,7 +17,7 @@ trait DatabaseBase 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ 'name' => 'Actors', - 'read' => ['*'], + 'read' => ['role:all'], 'write' => ['role:member', 'role:admin'], 'rules' => [ [ @@ -53,7 +53,7 @@ trait DatabaseBase 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ 'name' => 'Movies', - 'read' => ['*'], + 'read' => ['role:all'], 'write' => ['role:member', 'role:admin'], 'rules' => [ [ @@ -553,7 +553,7 @@ trait DatabaseBase 'releaseYear' => 1945, 'actors' => [], ], - 'read' => ['*'], + 'read' => ['role:all'], ]); $this->assertEquals($document['headers']['status-code'], 200); @@ -563,14 +563,14 @@ trait DatabaseBase if($this->getSide() == 'client') { $this->assertCount(1, $document['body']['$permissions']['read']); $this->assertCount(1, $document['body']['$permissions']['write']); - $this->assertEquals(['*'], $document['body']['$permissions']['read']); + $this->assertEquals(['role:all'], $document['body']['$permissions']['read']); $this->assertEquals(['user:'.$this->getUser()['$id']], $document['body']['$permissions']['write']); } if($this->getSide() == 'server') { $this->assertCount(1, $document['body']['$permissions']['read']); $this->assertCount(0, $document['body']['$permissions']['write']); - $this->assertEquals(['*'], $document['body']['$permissions']['read']); + $this->assertEquals(['role:all'], $document['body']['$permissions']['read']); $this->assertEquals([], $document['body']['$permissions']['write']); } @@ -586,14 +586,14 @@ trait DatabaseBase if($this->getSide() == 'client') { $this->assertCount(1, $document['body']['$permissions']['read']); $this->assertCount(1, $document['body']['$permissions']['write']); - $this->assertEquals(['*'], $document['body']['$permissions']['read']); + $this->assertEquals(['role:all'], $document['body']['$permissions']['read']); $this->assertEquals(['user:'.$this->getUser()['$id']], $document['body']['$permissions']['write']); } if($this->getSide() == 'server') { $this->assertCount(1, $document['body']['$permissions']['read']); $this->assertCount(0, $document['body']['$permissions']['write']); - $this->assertEquals(['*'], $document['body']['$permissions']['read']); + $this->assertEquals(['role:all'], $document['body']['$permissions']['read']); $this->assertEquals([], $document['body']['$permissions']['write']); } diff --git a/tests/e2e/Services/Database/DatabaseCustomServerTest.php b/tests/e2e/Services/Database/DatabaseCustomServerTest.php index 5590728756..5fdc876891 100644 --- a/tests/e2e/Services/Database/DatabaseCustomServerTest.php +++ b/tests/e2e/Services/Database/DatabaseCustomServerTest.php @@ -26,7 +26,7 @@ class DatabaseCustomServerTest extends Scope 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ 'name' => 'Actors', - 'read' => ['*'], + 'read' => ['role:all'], 'write' => ['role:member', 'role:admin'], 'rules' => [ [ diff --git a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php index 12a9edf245..b6abc58bb7 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php @@ -139,7 +139,7 @@ class FunctionsCustomClientTest extends Scope 'x-appwrite-key' => $apikey, ], [ 'name' => 'Test', - 'execute' => ['*'], + 'execute' => ['role:all'], 'env' => 'php-8.0', 'vars' => [ 'funcKey1' => 'funcValue1', diff --git a/tests/e2e/Services/Storage/StorageBase.php b/tests/e2e/Services/Storage/StorageBase.php index 09ec60724d..508866b6a1 100644 --- a/tests/e2e/Services/Storage/StorageBase.php +++ b/tests/e2e/Services/Storage/StorageBase.php @@ -18,8 +18,8 @@ trait StorageBase 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ 'file' => new CURLFile(realpath(__DIR__ . '/../../../resources/logo.png'), 'image/png', 'logo.png'), - 'read' => ['*'], - 'write' => ['*'], + 'read' => ['role:all'], + 'write' => ['role:all'], ]); $this->assertEquals($file['headers']['status-code'], 201); @@ -185,8 +185,8 @@ trait StorageBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'read' => ['*'], - 'write' => ['*'], + 'read' => ['role:all'], + 'write' => ['role:all'], ]); $this->assertEquals(200, $file['headers']['status-code']); diff --git a/tests/e2e/Services/Webhooks/WebhooksBase.php b/tests/e2e/Services/Webhooks/WebhooksBase.php index ac14514d3d..dfbdc524c7 100644 --- a/tests/e2e/Services/Webhooks/WebhooksBase.php +++ b/tests/e2e/Services/Webhooks/WebhooksBase.php @@ -18,8 +18,8 @@ trait WebhooksBase 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ 'name' => 'Actors', - 'read' => ['*'], - 'write' => ['*'], + 'read' => ['role:all'], + 'write' => ['role:all'], 'rules' => [ [ 'label' => 'First Name', @@ -79,8 +79,8 @@ trait WebhooksBase 'lastName' => 'Evans', ], - 'read' => ['*'], - 'write' => ['*'], + 'read' => ['role:all'], + 'write' => ['role:all'], ]); $this->assertEquals($document['headers']['status-code'], 201); @@ -122,8 +122,8 @@ trait WebhooksBase 'firstName' => 'Chris1', 'lastName' => 'Evans2', ], - 'read' => ['*'], - 'write' => ['*'], + 'read' => ['role:all'], + 'write' => ['role:all'], ]); $this->assertEquals($document['headers']['status-code'], 200); @@ -164,8 +164,8 @@ trait WebhooksBase 'lastName' => 'Cooper', ], - 'read' => ['*'], - 'write' => ['*'], + 'read' => ['role:all'], + 'write' => ['role:all'], ]); $this->assertEquals($document['headers']['status-code'], 201); @@ -209,8 +209,8 @@ trait WebhooksBase 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ 'file' => new CURLFile(realpath(__DIR__ . '/../../../resources/logo.png'), 'image/png', 'logo.png'), - 'read' => ['*'], - 'write' => ['*'], + 'read' => ['role:all'], + 'write' => ['role:all'], 'folderId' => 'xyz', ]); @@ -254,8 +254,8 @@ trait WebhooksBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'read' => ['*'], - 'write' => ['*'], + 'read' => ['role:all'], + 'write' => ['role:all'], ]); $this->assertEquals($file['headers']['status-code'], 200); diff --git a/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php b/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php index 0ef01ec850..adf2858c97 100644 --- a/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php +++ b/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php @@ -28,8 +28,8 @@ class WebhooksCustomServerTest extends Scope 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ 'name' => 'Actors1', - 'read' => ['*'], - 'write' => ['*'], + 'read' => ['role:all'], + 'write' => ['role:all'], 'rules' => [ [ 'label' => 'First Name', @@ -86,8 +86,8 @@ class WebhooksCustomServerTest extends Scope 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ 'name' => 'Demo', - 'read' => ['*'], - 'write' => ['*'], + 'read' => ['role:all'], + 'write' => ['role:all'], 'rules' => [ [ 'label' => 'First Name', @@ -307,7 +307,7 @@ class WebhooksCustomServerTest extends Scope ], $this->getHeaders()), [ 'name' => 'Test', 'env' => 'php-8.0', - 'execute' => ['*'], + 'execute' => ['role:all'], 'timeout' => 10, ]); @@ -349,7 +349,7 @@ class WebhooksCustomServerTest extends Scope ], $this->getHeaders()), [ 'name' => 'Test', 'env' => 'php-8.0', - 'execute' => ['*'], + 'execute' => ['role:all'], 'vars' => [ 'key1' => 'value1', ] diff --git a/tests/unit/Database/Validator/AuthorizationTest.php b/tests/unit/Database/Validator/AuthorizationTest.php index d46fec3bc6..87aa6da873 100644 --- a/tests/unit/Database/Validator/AuthorizationTest.php +++ b/tests/unit/Database/Validator/AuthorizationTest.php @@ -25,7 +25,7 @@ class AuthorizationTest extends TestCase '$collection' => uniqid(), '$permissions' => [ 'read' => ['user:123', 'team:123'], - 'write' => ['*'], + 'write' => ['role:all'], ], ]); $this->object = new Authorization($this->document, 'read'); @@ -45,7 +45,7 @@ class AuthorizationTest extends TestCase $this->assertEquals(Authorization::isRole('user:456'), true); $this->assertEquals(Authorization::isRole('user:457'), false); $this->assertEquals(Authorization::isRole(''), false); - $this->assertEquals(Authorization::isRole('*'), true); + $this->assertEquals(Authorization::isRole('role:all'), true); $this->assertEquals($this->object->isValid($this->document->getPermissions()), true); diff --git a/tests/unit/Utopia/Filters/V06Test.php b/tests/unit/Utopia/Filters/V06Test.php index 6bc98e0137..28891f32c1 100644 --- a/tests/unit/Utopia/Filters/V06Test.php +++ b/tests/unit/Utopia/Filters/V06Test.php @@ -432,7 +432,7 @@ class V06Test extends TestCase { $content = [ '$id' => '5e5ea5c16897e', - '$permissions' => ['read' => ['*'], 'write' => ['*']], + '$permissions' => ['read' => ['role:all'], 'write' => ['role:all']], 'name' => 'Pink.png', 'dateCreated' => 1592981250, 'signature' => '5d529fd02b544198ae075bd57c1762bb', @@ -444,7 +444,7 @@ class V06Test extends TestCase $parsedResponse = $this->filter->parse($content, $model); $this->assertEquals($parsedResponse['$id'], '5e5ea5c16897e'); - $this->assertEquals($parsedResponse['$permissions'], ['read' => ['*'], 'write' => ['*']]); + $this->assertEquals($parsedResponse['$permissions'], ['read' => ['role:all'], 'write' => ['role:all']]); $this->assertEquals($parsedResponse['name'], 'Pink.png'); $this->assertEquals($parsedResponse['dateCreated'], 1592981250); $this->assertEquals($parsedResponse['signature'], '5d529fd02b544198ae075bd57c1762bb'); @@ -467,7 +467,7 @@ class V06Test extends TestCase { $content = [ '$id' => '5e5ea5c16897e', - '$permissions' => ['read' => ['*'], 'write' => ['*']], + '$permissions' => ['read' => ['role:all'], 'write' => ['role:all']], 'name' => 'Movies', 'dateCreated' => 1592981250, 'dateUpdated' => '5d529fd02b544198ae075bd57c1762bb', @@ -478,7 +478,7 @@ class V06Test extends TestCase $parsedResponse = $this->filter->parse($content, $model); $this->assertEquals($parsedResponse['$id'], '5e5ea5c16897e'); - $this->assertEquals($parsedResponse['$permissions'], ['read' => ['*'], 'write' => ['*']]); + $this->assertEquals($parsedResponse['$permissions'], ['read' => ['role:all'], 'write' => ['role:all']]); $this->assertEquals($parsedResponse['name'], 'Movies'); $this->assertEquals($parsedResponse['dateCreated'], 1592981250); $this->assertEquals($parsedResponse['dateUpdated'], '5d529fd02b544198ae075bd57c1762bb'); @@ -495,7 +495,7 @@ class V06Test extends TestCase 'collections' => [ 0 => [ '$id' => '5e5ea5c16897e', - '$permissions' => ['read' => ['*'], 'write' => ['*']], + '$permissions' => ['read' => ['role:all'], 'write' => ['role:all']], 'name' => 'Movies', 'dateCreated' => 1592981250, 'dateUpdated' => '5d529fd02b544198ae075bd57c1762bb', @@ -509,7 +509,7 @@ class V06Test extends TestCase $this->assertEquals($parsedResponse['sum'], 1); $this->assertEquals($parsedResponse['collections'][0]['$id'], '5e5ea5c16897e'); - $this->assertEquals($parsedResponse['collections'][0]['$permissions'], ['read' => ['*'], 'write' => ['*']]); + $this->assertEquals($parsedResponse['collections'][0]['$permissions'], ['read' => ['role:all'], 'write' => ['role:all']]); $this->assertEquals($parsedResponse['collections'][0]['name'], 'Movies'); $this->assertEquals($parsedResponse['collections'][0]['dateCreated'], 1592981250); $this->assertEquals($parsedResponse['collections'][0]['dateUpdated'], '5d529fd02b544198ae075bd57c1762bb'); From b929084b8bbe589afb612c22510a2dd9199fa50d Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Fri, 11 Jun 2021 21:23:31 +0300 Subject: [PATCH 45/60] Updated var name --- app/controllers/api/storage.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index 246209f9a5..d4d63ecf47 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -436,11 +436,11 @@ App::get('/v1/storage/files/:fileId/view') ->param('fileId', '', new UID(), 'File unique ID.') ->inject('response') ->inject('dbForInternal') - ->action(function ($fileId, $response, $projectDB) { + ->action(function ($fileId, $response, $dbForInternal) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForInternal */ - $file = $projectDB->getDocument('files', $fileId); + $file = $dbForInternal->getDocument('files', $fileId); $mimes = Config::getParam('storage-mimes'); if (empty($file->getId())) { From 2028d334a25da2e986688fbd0bdf635ef2798c35 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Fri, 11 Jun 2021 21:23:41 +0300 Subject: [PATCH 46/60] Fixed old DB --- app/controllers/api/projects.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index c754c4a2d0..ea14607674 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -46,11 +46,13 @@ App::post('/v1/projects') ->inject('dbForConsole') ->inject('dbForInternal') ->inject('dbForExternal') - ->action(function ($name, $teamId, $description, $logo, $url, $legalName, $legalCountry, $legalState, $legalCity, $legalAddress, $legalTaxId, $response, $dbForConsole, $dbForInternal, $dbForExternal) { + ->inject('consoleDB') + ->action(function ($name, $teamId, $description, $logo, $url, $legalName, $legalCountry, $legalState, $legalCity, $legalAddress, $legalTaxId, $response, $dbForConsole, $dbForInternal, $dbForExternal, $consoleDB) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForConsole */ /** @var Utopia\Database\Database $dbForInternal */ /** @var Utopia\Database\Database $dbForExternal */ + /** @var Appwrite\Database\Database $consoleDB */ $team = $dbForConsole->getDocument('teams', $teamId); @@ -120,6 +122,8 @@ App::post('/v1/projects') } } + $consoleDB->createNamespace($project->getId()); + $response->setStatusCode(Response::STATUS_CODE_CREATED); $response->dynamic2($project, Response::MODEL_PROJECT); }); From 5fa2a8468ec09cedc6bbe40eecaa46f3b7cb200f Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Fri, 11 Jun 2021 23:39:00 +0300 Subject: [PATCH 47/60] Fixed test and PDO error --- app/controllers/api/account.php | 5 +++-- app/controllers/api/teams.php | 1 - src/Appwrite/Extend/PDO.php | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 6a3799416c..cfd65f13d2 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -602,7 +602,7 @@ App::post('/v1/account/sessions/anonymous') throw new Exception('Failed to create anonymous user.', 401); } - if ($user->getId()) { + if (!$user->isEmpty()) { throw new Exception('Cannot create an anonymous user when logged in.', 401); } @@ -662,7 +662,8 @@ App::post('/v1/account/sessions/anonymous') Authorization::setRole('user:'.$user->getId()); - $user = $dbForInternal->updateDocument('users', $user->getId(), $user); + $user = $dbForInternal->updateDocument('users', $user->getId(), + $user->setAttribute('sessions', $session, Document::SET_TYPE_APPEND)); $audits ->setParam('userId', $user->getId()) diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index 588651f705..e18f2e7e14 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -456,7 +456,6 @@ App::get('/v1/teams/:teamId/memberships') ]), Response::MODEL_MEMBERSHIP_LIST); }); - App::patch('/v1/teams/:teamId/memberships/:membershipId') ->desc('Update Membership Roles') ->groups(['api', 'teams']) diff --git a/src/Appwrite/Extend/PDO.php b/src/Appwrite/Extend/PDO.php index d5b2cab94c..a46da92e0a 100644 --- a/src/Appwrite/Extend/PDO.php +++ b/src/Appwrite/Extend/PDO.php @@ -77,6 +77,7 @@ class PDO extends PDONative $result = $this->pdo->rollBack(); } catch (\Throwable $th) { $this->pdo = $this->reconnect(); + return false; } return $result; From 034a01af105f6228be9ac98dad397fc494e03e6d Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sat, 12 Jun 2021 13:07:26 +0300 Subject: [PATCH 48/60] Updated deletes worker --- app/workers/deletes.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/workers/deletes.php b/app/workers/deletes.php index 5316783c49..1ca7532e62 100644 --- a/app/workers/deletes.php +++ b/app/workers/deletes.php @@ -32,14 +32,15 @@ class DeletesV1 public function perform() { - $projectId = isset($this->args['projectId']) ? $this->args['projectId'] : ''; - $type = $this->args['type']; + $projectId = $this->args['projectId'] ?? ''; + $type = $this->args['type'] ?? ''; switch (strval($type)) { case DELETE_TYPE_DOCUMENT: - $document = $this->args['document']; + $document = $this->args['document'] ?? ''; $document = new Document($document); - switch (strval($document->getCollection())) { + + switch ($document->getCollection()) { case Database::SYSTEM_COLLECTION_PROJECTS: $this->deleteProject($document); break; @@ -78,7 +79,6 @@ class DeletesV1 default: Console::error('No delete operation for type: '.$type); break; - } } From a6acb1724bfc643a088bd9c3465dcdf280fb2524 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sat, 12 Jun 2021 15:00:17 +0300 Subject: [PATCH 49/60] Fixed wrong indexes --- app/config/collections2.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/config/collections2.php b/app/config/collections2.php index 79aa3637ca..b130092886 100644 --- a/app/config/collections2.php +++ b/app/config/collections2.php @@ -629,7 +629,7 @@ $collections = [ 'indexes' => [ [ '$id' => '_key_bucket', - 'type' => Database::INDEX_UNIQUE, + 'type' => Database::INDEX_KEY, 'attributes' => ['bucketId'], 'lengths' => [Database::LENGTH_KEY], 'orders' => [Database::ORDER_ASC], @@ -852,7 +852,7 @@ $collections = [ 'indexes' => [ [ '$id' => '_key_function', - 'type' => Database::INDEX_UNIQUE, + 'type' => Database::INDEX_KEY, 'attributes' => ['functionId'], 'lengths' => [Database::LENGTH_KEY], 'orders' => [Database::ORDER_ASC], @@ -960,7 +960,7 @@ $collections = [ 'indexes' => [ [ '$id' => '_key_function', - 'type' => Database::INDEX_UNIQUE, + 'type' => Database::INDEX_KEY, 'attributes' => ['functionId'], 'lengths' => [Database::LENGTH_KEY], 'orders' => [Database::ORDER_ASC], From 4dc225a886969200070badae65abece4ba8f447c Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sat, 12 Jun 2021 17:33:23 +0300 Subject: [PATCH 50/60] Added orderType params --- app/controllers/api/functions.php | 11 ++++++----- app/controllers/api/projects.php | 2 +- app/controllers/api/teams.php | 2 +- app/controllers/api/users.php | 2 +- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index 10ad3dece4..78c2391dd4 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -12,6 +12,7 @@ use Appwrite\Utopia\Response; use Appwrite\Task\Validator\Cron; use Utopia\App; use Utopia\Exception; +use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Query; use Utopia\Database\Validator\Authorization; @@ -499,13 +500,13 @@ App::get('/v1/functions/:functionId/tags') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_TAG_LIST) ->param('functionId', '', new UID(), 'Function unique ID.') - // ->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true) + ->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true) ->param('limit', 25, new Range(0, 100), 'Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true) ->param('offset', 0, new Range(0, 2000), 'Results offset. The default value is 0. Use this param to manage pagination.', true) - // ->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true) + ->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true) ->inject('response') ->inject('dbForInternal') - ->action(function ($functionId, $limit, $offset, $response, $dbForInternal) { + ->action(function ($functionId, $search, $limit, $offset, $orderType, $response, $dbForInternal) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForInternal */ @@ -517,7 +518,7 @@ App::get('/v1/functions/:functionId/tags') $queries[] = new Query('functionId', Query::TYPE_EQUAL, [$function->getId()]); - $results = $dbForInternal->find('tags', $queries, $limit, $offset); + $results = $dbForInternal->find('tags', $queries, $limit, $offset, ['_id'], [$orderType]); $sum = $dbForInternal->count('tags', $queries, APP_LIMIT_COUNT); $response->dynamic2(new Document([ @@ -759,7 +760,7 @@ App::get('/v1/functions/:functionId/executions') $results = $dbForInternal->find('executions', [ new Query('functionId', Query::TYPE_EQUAL, [$function->getId()]), - ], $limit, $offset); + ], $limit, $offset, ['_id'], [Database::ORDER_DESC]); $sum = $dbForInternal->count('executions', [ new Query('functionId', Query::TYPE_EQUAL, [$function->getId()]), diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index ea14607674..dcc840bc09 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -150,7 +150,7 @@ App::get('/v1/projects') $queries = ($search) ? [new Query('name', Query::TYPE_SEARCH, [$search])] : []; - $results = $dbForConsole->find('projects', $queries, $limit, $offset); + $results = $dbForConsole->find('projects', $queries, $limit, $offset, ['_id'], [$orderType]); $sum = $dbForConsole->count('projects', $queries, APP_LIMIT_COUNT); $response->dynamic2(new Document([ diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index e18f2e7e14..f8c3d25822 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -436,7 +436,7 @@ App::get('/v1/teams/:teamId/memberships') throw new Exception('Team not found', 404); } - $memberships = $dbForInternal->find('memberships', [new Query('teamId', Query::TYPE_EQUAL, [$teamId])], $limit, $offset); + $memberships = $dbForInternal->find('memberships', [new Query('teamId', Query::TYPE_EQUAL, [$teamId])], $limit, $offset, ['_id'], [$orderType]); $sum = $dbForInternal->count('memberships', [new Query('teamId', Query::TYPE_EQUAL, [$teamId])], APP_LIMIT_COUNT); $users = []; diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index b1fb0aaf5a..686f31b3e1 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -90,7 +90,7 @@ App::get('/v1/users') /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForInternal */ - $results = $dbForInternal->find('users', [], $limit, $offset); + $results = $dbForInternal->find('users', [], $limit, $offset, ['_id'], [$orderType]); $sum = $dbForInternal->count('users', [], APP_LIMIT_COUNT); $response->dynamic2(new Document([ From babf31c3a3bfc72ce0336d6603ee2edd673e9d83 Mon Sep 17 00:00:00 2001 From: "Eldad A. Fux" Date: Sat, 12 Jun 2021 17:50:27 +0300 Subject: [PATCH 51/60] Update app/controllers/shared/api.php Co-authored-by: kodumbeats --- app/controllers/shared/api.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index cb0a9e2e42..14555b502f 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -65,7 +65,7 @@ App::init(function ($utopia, $request, $response, $project, $user, $register, $e $isAppUser = Auth::isAppUser(Authorization::$roles); if (($abuse->check() // Route is rate-limited - && App::getEnv('_APP_OPTIONS_ABUSE', 'enabled') !== 'disabled') // Abuse is not diabled + && App::getEnv('_APP_OPTIONS_ABUSE', 'enabled') !== 'disabled') // Abuse is not disabled && (!$isAppUser && !$isPrivilegedUser)) // User is not an admin or API key { throw new Exception('Too many requests', 429); From 6fa0fc6ac996ab196949c8f30538f26bad11d68a Mon Sep 17 00:00:00 2001 From: "Eldad A. Fux" Date: Sat, 12 Jun 2021 17:50:35 +0300 Subject: [PATCH 52/60] Update app/workers/audits.php Co-authored-by: kodumbeats --- app/workers/audits.php | 1 - 1 file changed, 1 deletion(-) diff --git a/app/workers/audits.php b/app/workers/audits.php index 237b44258b..b67a3f8fee 100644 --- a/app/workers/audits.php +++ b/app/workers/audits.php @@ -1,7 +1,6 @@ Date: Sat, 12 Jun 2021 17:50:40 +0300 Subject: [PATCH 53/60] Update app/controllers/api/account.php Co-authored-by: kodumbeats --- app/controllers/api/account.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 91be0299bd..a2b336f20c 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -18,7 +18,6 @@ use Utopia\Validator\Text; use Utopia\Validator\WhiteList; use Utopia\Validator\ArrayList; use Utopia\Audit\Audit; -use Utopia\Audit\Adapters\MySQL as AuditAdapter; use Utopia\Database\Document; use Utopia\Database\Exception\Duplicate; use Utopia\Database\Query; @@ -1620,4 +1619,4 @@ App::put('/v1/account/verification') ; $response->dynamic2($verification, Response::MODEL_TOKEN); - }); \ No newline at end of file + }); From 5be8e2fc0cff2ea7b087974aa0319681c5fbcc00 Mon Sep 17 00:00:00 2001 From: "Eldad A. Fux" Date: Sat, 12 Jun 2021 17:50:45 +0300 Subject: [PATCH 54/60] Update app/workers/deletes.php Co-authored-by: kodumbeats --- app/workers/deletes.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/workers/deletes.php b/app/workers/deletes.php index b83a7c4c23..c4a307def2 100644 --- a/app/workers/deletes.php +++ b/app/workers/deletes.php @@ -13,7 +13,6 @@ use Utopia\Abuse\Adapters\TimeLimit; use Utopia\CLI\Console; use Utopia\Config\Config; use Utopia\Audit\Audit; -use Utopia\Audit\Adapters\MySQL as AuditAdapter; use Utopia\Cache\Cache; use Utopia\Database\Adapter\MariaDB; @@ -388,4 +387,4 @@ class DeletesV1 return $dbForInternal; } -} \ No newline at end of file +} From f3ff50d686dff9416ff97540646c5ea4f437f1d8 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sat, 12 Jun 2021 21:39:59 +0300 Subject: [PATCH 55/60] Fixed logs time attribute --- app/controllers/api/account.php | 3 ++- app/controllers/api/users.php | 3 ++- app/http.php | 4 ++-- src/Appwrite/Utopia/Response/Model/Log.php | 6 ++++++ 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 09840733f1..f75a7f65c5 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -872,9 +872,10 @@ App::get('/v1/account/logs') $detector = new Detector($log['userAgent']); $output[$i] = new Document(array_merge([ + 'userId' => $log['userId'], 'event' => $log['event'], 'ip' => $log['ip'], - 'time' => \strtotime($log['time']), + 'time' => $log['time'], ], $detector->getOS(), $detector->getClient(), $detector->getDevice())); $record = $geodb->get($log['ip']); diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 3a6fcbe482..9ade946cfe 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -277,9 +277,10 @@ App::get('/v1/users/:userId/logs') $clientEngineVersion = (isset($client['engine_version'])) ? $client['engine_version'] : ''; $output[$i] = new Document([ + 'userId' => $log['userId'], 'event' => $log['event'], 'ip' => $log['ip'], - 'time' => \strtotime($log['time']), + 'time' => $log['time'], 'osCode' => $osCode, 'osName' => $osName, diff --git a/app/http.php b/app/http.php index ea2df6b95e..0e67e07c31 100644 --- a/app/http.php +++ b/app/http.php @@ -3,8 +3,6 @@ require_once __DIR__.'/../vendor/autoload.php'; use Appwrite\Database\Validator\Authorization; -use Utopia\Swoole\Files; -use Utopia\Swoole\Request; use Appwrite\Utopia\Response; use Swoole\Process; use Swoole\Http\Server; @@ -16,6 +14,8 @@ use Utopia\Config\Config; use Utopia\Database\Validator\Authorization as Authorization2; use Utopia\Audit\Audit; use Utopia\Abuse\Adapters\TimeLimit; +use Utopia\Swoole\Files; +use Utopia\Swoole\Request; // xdebug_start_trace('/tmp/trace'); diff --git a/src/Appwrite/Utopia/Response/Model/Log.php b/src/Appwrite/Utopia/Response/Model/Log.php index baf2f83347..051fcde841 100644 --- a/src/Appwrite/Utopia/Response/Model/Log.php +++ b/src/Appwrite/Utopia/Response/Model/Log.php @@ -10,6 +10,12 @@ class Log extends Model public function __construct() { $this + ->addRule('userId', [ + 'type' => self::TYPE_STRING, + 'description' => 'Event name.', + 'default' => '', + 'example' => 'account.sessions.create', + ]) ->addRule('event', [ 'type' => self::TYPE_STRING, 'description' => 'Event name.', From 640dbd3e8bc75a39d2e89185310a7c30c2c860bb Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sat, 12 Jun 2021 21:40:08 +0300 Subject: [PATCH 56/60] Updated DB lib --- composer.lock | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/composer.lock b/composer.lock index 472c3c761e..08a5a88de3 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "e3617b2fd699e599dc2c1d7ed74c9f44", + "content-hash": "bce8a47d7c5e94cc18bd05b1229ad1db", "packages": [ { "name": "adhocore/jwt", @@ -1607,12 +1607,12 @@ "source": { "type": "git", "url": "https://github.com/lohanidamodar/abuse", - "reference": "4f3349b3c1c85353708d13f7eb3c34c0762d4828" + "reference": "a7de2639b107af742c21ddd80b54e9edfb540fce" }, "require": { "ext-pdo": "*", "php": ">=7.4", - "utopia-php/database": "0.2.*" + "utopia-php/database": "0.3.*" }, "require-dev": { "phpunit/phpunit": "^9.4", @@ -1641,7 +1641,7 @@ "upf", "utopia" ], - "time": "2021-06-09T09:11:16+00:00" + "time": "2021-06-10T11:05:08+00:00" }, { "name": "utopia-php/analytics", @@ -1704,12 +1704,12 @@ "source": { "type": "git", "url": "https://github.com/lohanidamodar/audit", - "reference": "b3b85524717bbf52cfe71f01474c4012bfd4323a" + "reference": "6a3a3cc60e9240f2e8997755b4b8e0f9b8c557aa" }, "require": { "ext-pdo": "*", "php": ">=7.4", - "utopia-php/database": "0.2.*" + "utopia-php/database": "0.3.*" }, "require-dev": { "phpunit/phpunit": "^9.3", @@ -1738,7 +1738,7 @@ "upf", "utopia" ], - "time": "2021-06-09T09:10:27+00:00" + "time": "2021-06-10T11:05:17+00:00" }, { "name": "utopia-php/cache", @@ -1899,16 +1899,16 @@ }, { "name": "utopia-php/database", - "version": "0.3.1", + "version": "0.3.2", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "6f8b7184ae4971672188607ea311a22a50da6767" + "reference": "8e56a2d399d17b2497c8ee0f8bf429ac2da90c56" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/6f8b7184ae4971672188607ea311a22a50da6767", - "reference": "6f8b7184ae4971672188607ea311a22a50da6767", + "url": "https://api.github.com/repos/utopia-php/database/zipball/8e56a2d399d17b2497c8ee0f8bf429ac2da90c56", + "reference": "8e56a2d399d17b2497c8ee0f8bf429ac2da90c56", "shasum": "" }, "require": { @@ -1956,9 +1956,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.3.1" + "source": "https://github.com/utopia-php/database/tree/0.3.2" }, - "time": "2021-06-11T15:02:46+00:00" + "time": "2021-06-12T18:26:26+00:00" }, { "name": "utopia-php/domains", From 7a3221168e1ac2ee4606b5101f4bf05194eb5fe1 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sat, 12 Jun 2021 21:43:12 +0300 Subject: [PATCH 57/60] Removed unrequired attribute --- app/controllers/api/account.php | 1 - app/controllers/api/users.php | 1 - src/Appwrite/Utopia/Response/Model/Log.php | 6 ------ 3 files changed, 8 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index f75a7f65c5..df0639848d 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -872,7 +872,6 @@ App::get('/v1/account/logs') $detector = new Detector($log['userAgent']); $output[$i] = new Document(array_merge([ - 'userId' => $log['userId'], 'event' => $log['event'], 'ip' => $log['ip'], 'time' => $log['time'], diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 9ade946cfe..ab90647658 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -277,7 +277,6 @@ App::get('/v1/users/:userId/logs') $clientEngineVersion = (isset($client['engine_version'])) ? $client['engine_version'] : ''; $output[$i] = new Document([ - 'userId' => $log['userId'], 'event' => $log['event'], 'ip' => $log['ip'], 'time' => $log['time'], diff --git a/src/Appwrite/Utopia/Response/Model/Log.php b/src/Appwrite/Utopia/Response/Model/Log.php index 051fcde841..baf2f83347 100644 --- a/src/Appwrite/Utopia/Response/Model/Log.php +++ b/src/Appwrite/Utopia/Response/Model/Log.php @@ -10,12 +10,6 @@ class Log extends Model public function __construct() { $this - ->addRule('userId', [ - 'type' => self::TYPE_STRING, - 'description' => 'Event name.', - 'default' => '', - 'example' => 'account.sessions.create', - ]) ->addRule('event', [ 'type' => self::TYPE_STRING, 'description' => 'Event name.', From b4fcbbe0628666771390ce3ffb0131403ccc856f Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sat, 12 Jun 2021 22:37:19 +0300 Subject: [PATCH 58/60] Fixed JWT missing namespace --- app/controllers/api/account.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index df0639848d..3f64bd0b5d 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -1,5 +1,6 @@ Date: Sat, 12 Jun 2021 23:44:25 +0300 Subject: [PATCH 59/60] Returned userIDs --- app/controllers/api/account.php | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 3f64bd0b5d..5d9e121772 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -179,6 +179,7 @@ App::post('/v1/account/sessions') $session = new Document(array_merge( [ '$id' => $dbForInternal->getId(), + 'userId' => $profile->getId(), 'provider' => Auth::SESSION_PROVIDER_EMAIL, 'providerUid' => $email, 'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak @@ -498,6 +499,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') $expiry = \time() + Auth::TOKEN_EXPIRATION_LOGIN_LONG; $session = new Document(array_merge([ '$id' => $dbForInternal->getId(), + 'userId' => $user->getId(), 'provider' => $provider, 'providerUid' => $oauth2ID, 'providerToken' => $accessToken, @@ -647,6 +649,7 @@ App::post('/v1/account/sessions/anonymous') $session = new Document(array_merge( [ '$id' => $dbForInternal->getId(), + 'userId' => $user->getId(), 'provider' => Auth::SESSION_PROVIDER_ANONYMOUS, 'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak 'expire' => $expiry, @@ -1135,13 +1138,15 @@ App::delete('/v1/account/sessions/:sessionId') ->inject('response') ->inject('user') ->inject('dbForInternal') + ->inject('locale') ->inject('audits') ->inject('events') - ->action(function ($sessionId, $request, $response, $user, $dbForInternal, $audits, $events) { + ->action(function ($sessionId, $request, $response, $user, $dbForInternal, $locale, $audits, $events) { /** @var Utopia\Swoole\Request $request */ /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Document $user */ /** @var Utopia\Database\Database $dbForInternal */ + /** @var Utopia\Locale\Locale $locale */ /** @var Appwrite\Event\Event $audits */ /** @var Appwrite\Event\Event $events */ @@ -1167,7 +1172,10 @@ App::delete('/v1/account/sessions/:sessionId') $session->setAttribute('current', false); if ($session->getAttribute('secret') == Auth::hash(Auth::$secret)) { // If current session delete the cookies too - $session->setAttribute('current', true); + $session + ->setAttribute('current', true) + ->setAttribute('countryName', (isset($countries[strtoupper($session->getAttribute('countryCode'))])) ? $countries[strtoupper($session->getAttribute('countryCode'))] : $locale->getText('locale.country.unknown')) + ; if (!Config::getParam('domainVerification')) { $response @@ -1210,13 +1218,15 @@ App::delete('/v1/account/sessions') ->inject('response') ->inject('user') ->inject('dbForInternal') + ->inject('locale') ->inject('audits') ->inject('events') - ->action(function ($request, $response, $user, $dbForInternal, $audits, $events) { + ->action(function ($request, $response, $user, $dbForInternal, $locale, $audits, $events) { /** @var Utopia\Swoole\Request $request */ /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Document $user */ /** @var Utopia\Database\Database $dbForInternal */ + /** @var Utopia\Locale\Locale $locale */ /** @var Appwrite\Event\Event $audits */ /** @var Appwrite\Event\Event $events */ @@ -1238,7 +1248,10 @@ App::delete('/v1/account/sessions') ; } - $session->setAttribute('current', false); + $session + ->setAttribute('current', false) + ->setAttribute('countryName', (isset($countries[strtoupper($session->getAttribute('countryCode'))])) ? $countries[strtoupper($session->getAttribute('countryCode'))] : $locale->getText('locale.country.unknown')) + ; if ($session->getAttribute('secret') == Auth::hash(Auth::$secret)) { // If current session delete the cookies too $session->setAttribute('current', true); @@ -1312,6 +1325,7 @@ App::post('/v1/account/recovery') $secret = Auth::tokenGenerator(); $recovery = new Document([ '$id' => $dbForInternal->getId(), + 'userId' => $profile->getId(), 'type' => Auth::TOKEN_TYPE_RECOVERY, 'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak 'expire' => \time() + Auth::TOKEN_EXPIRATION_RECOVERY, @@ -1495,6 +1509,7 @@ App::post('/v1/account/verification') $verification = new Document([ '$id' => $dbForInternal->getId(), + 'userId' => $user->getId(), 'type' => Auth::TOKEN_TYPE_VERIFICATION, 'secret' => Auth::hash($verificationSecret), // One way hash encryption to protect DB leak 'expire' => \time() + Auth::TOKEN_EXPIRATION_CONFIRM, From 9f81790761a5a1d0e627dcc7def56b3aac852f66 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sun, 13 Jun 2021 18:01:56 +0300 Subject: [PATCH 60/60] Updates to swagger tests --- app/controllers/web/home.php | 1 + composer.lock | 8 ++++---- docker-compose.yml | 11 +++++++++++ tests/e2e/General/HTTPTest.php | 10 ++++++---- tests/resources/swagger2.json | 1 + 5 files changed, 23 insertions(+), 8 deletions(-) create mode 100644 tests/resources/swagger2.json diff --git a/app/controllers/web/home.php b/app/controllers/web/home.php index 8282539544..f4325f7b44 100644 --- a/app/controllers/web/home.php +++ b/app/controllers/web/home.php @@ -233,6 +233,7 @@ App::get('/specs/:format') ->groups(['web', 'home']) ->label('scope', 'public') ->label('docs', false) + ->label('origin', '*') ->param('format', 'swagger2', new WhiteList(['swagger2', 'open-api3'], true), 'Spec format.', true) ->param('platform', APP_PLATFORM_CLIENT, new WhiteList([APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER, APP_PLATFORM_CONSOLE], true), 'Choose target platform.', true) ->param('tests', 0, function () {return new Range(0, 1);}, 'Include only test services.', true) diff --git a/composer.lock b/composer.lock index 08a5a88de3..20e5ea98ae 100644 --- a/composer.lock +++ b/composer.lock @@ -1607,7 +1607,7 @@ "source": { "type": "git", "url": "https://github.com/lohanidamodar/abuse", - "reference": "a7de2639b107af742c21ddd80b54e9edfb540fce" + "reference": "351dba60714321fabcd5028fdf7325f18f343018" }, "require": { "ext-pdo": "*", @@ -1641,7 +1641,7 @@ "upf", "utopia" ], - "time": "2021-06-10T11:05:08+00:00" + "time": "2021-06-13T07:41:20+00:00" }, { "name": "utopia-php/analytics", @@ -1704,7 +1704,7 @@ "source": { "type": "git", "url": "https://github.com/lohanidamodar/audit", - "reference": "6a3a3cc60e9240f2e8997755b4b8e0f9b8c557aa" + "reference": "b3ca9fa928fdec8c966596cbd85ee1141c79b6eb" }, "require": { "ext-pdo": "*", @@ -1738,7 +1738,7 @@ "upf", "utopia" ], - "time": "2021-06-10T11:05:17+00:00" + "time": "2021-06-13T07:41:15+00:00" }, { "name": "utopia-php/cache", diff --git a/docker-compose.yml b/docker-compose.yml index f6da6f43fa..be056b8a64 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -476,6 +476,17 @@ services: networks: - appwrite + swagger-validator: + image: 'swaggerapi/swagger-validator-v2:v2.0.5' + container_name: appwrite-swagger-validator + ports: + - '9506:8080' + environment: + - REJECT_LOCAL=false + - REJECT_REDIRECT=false + networks: + - appwrite + # redis-commander: # image: rediscommander/redis-commander:latest # restart: unless-stopped diff --git a/tests/e2e/General/HTTPTest.php b/tests/e2e/General/HTTPTest.php index 8d38b3e882..b5274ecd7b 100644 --- a/tests/e2e/General/HTTPTest.php +++ b/tests/e2e/General/HTTPTest.php @@ -96,7 +96,7 @@ class HTTPTest extends Scope public function testSpecSwagger2() { - $response = $this->client->call(Client::METHOD_GET, '/specs/swagger2?platform=client', [ + $response = $this->client->call(Client::METHOD_GET, '/specs/swagger2?platform=console', [ 'content-type' => 'application/json', ], []); @@ -105,11 +105,13 @@ class HTTPTest extends Scope } $client = new Client(); - $client->setEndpoint('https://validator.swagger.io'); + $client->setEndpoint('http://appwrite-swagger-validator:8080'); /** * Test for SUCCESS */ + http://localhost:9506/validator?url=http://petstore.swagger.io/v2/swagger.json + $response = $client->call(Client::METHOD_POST, '/validator/debug', [ 'content-type' => 'application/json', ], json_decode(file_get_contents(realpath(__DIR__ . '/../../resources/swagger2.json')), true)); @@ -124,7 +126,7 @@ class HTTPTest extends Scope public function testSpecOpenAPI3() { - $response = $this->client->call(Client::METHOD_GET, '/specs/open-api3?platform=client', [ + $response = $this->client->call(Client::METHOD_GET, '/specs/open-api3?platform=console', [ 'content-type' => 'application/json', ], []); @@ -133,7 +135,7 @@ class HTTPTest extends Scope } $client = new Client(); - $client->setEndpoint('https://validator.swagger.io'); + $client->setEndpoint('http://appwrite-swagger-validator:8080'); /** * Test for SUCCESS diff --git a/tests/resources/swagger2.json b/tests/resources/swagger2.json new file mode 100644 index 0000000000..6baa467873 --- /dev/null +++ b/tests/resources/swagger2.json @@ -0,0 +1 @@ +{"swagger":"2.0","info":{"version":"0.8.0","title":"Appwrite","description":"Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https:\/\/appwrite.io\/docs](https:\/\/appwrite.io\/docs)","termsOfService":"https:\/\/appwrite.io\/policy\/terms","contact":{"name":"Appwrite Team","url":"https:\/\/appwrite.io\/support","email":"team@appwrite.io"},"license":{"name":"BSD-3-Clause","url":"https:\/\/raw.githubusercontent.com\/appwrite\/appwrite\/master\/LICENSE"}},"host":"appwrite.io","basePath":"\/v1","schemes":["https"],"consumes":["application\/json","multipart\/form-data"],"produces":["application\/json"],"securityDefinitions":{"Project":{"type":"apiKey","name":"X-Appwrite-Project","description":"Your project ID","in":"header","x-appwrite":{"demo":"5df5acd0d48c2"}},"Key":{"type":"apiKey","name":"X-Appwrite-Key","description":"Your secret API key","in":"header","x-appwrite":{"demo":"919c2d18fb5d4...a2ae413da83346ad2"}},"JWT":{"type":"apiKey","name":"X-Appwrite-JWT","description":"Your secret JSON Web Token","in":"header","x-appwrite":{"demo":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ..."}},"Locale":{"type":"apiKey","name":"X-Appwrite-Locale","description":"","in":"header","x-appwrite":{"demo":"en"}},"Mode":{"type":"apiKey","name":"X-Appwrite-Mode","description":"","in":"header","x-appwrite":{"demo":""}}},"paths":{"\/account":{"get":{"summary":"Get Account","operationId":"accountGet","consumes":["application\/json"],"produces":["application\/json"],"tags":["account"],"description":"Get currently logged in user data as JSON object.","responses":{"200":{"description":"User","schema":{"$ref":"#\/definitions\/user"}}},"x-appwrite":{"method":"get","weight":44,"cookies":false,"type":"","demo":"account\/get.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/get.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client","server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"JWT":[]}]},"post":{"summary":"Create Account","operationId":"accountCreate","consumes":["application\/json"],"produces":["application\/json"],"tags":["account"],"description":"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#accountCreateVerification) route 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#accountCreateSession).","responses":{"201":{"description":"User","schema":{"$ref":"#\/definitions\/user"}}},"x-appwrite":{"method":"create","weight":36,"cookies":false,"type":"","demo":"account\/create.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/create.md","rate-limit":10,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"public","platforms":["client"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[]}],"parameters":[{"name":"payload","in":"body","schema":{"type":"object","properties":{"email":{"type":"string","description":"User email.","default":null,"x-example":"email@example.com"},"password":{"type":"string","description":"User password. Must be between 6 to 32 chars.","default":null,"x-example":"password"},"name":{"type":"string","description":"User name. Max length: 128 chars.","default":"","x-example":"[NAME]"}},"required":["email","password"]}}]},"delete":{"summary":"Delete Account","operationId":"accountDelete","consumes":["application\/json"],"produces":[],"tags":["account"],"description":"Delete a currently logged in user account. Behind the scene, the user record is not deleted but permanently blocked from any access. This is done to avoid deleted accounts being overtaken by new users with the same email address. Any user-related resources like documents or storage files should be deleted separately.","responses":{"204":{"description":"No content"}},"x-appwrite":{"method":"delete","weight":52,"cookies":false,"type":"","demo":"account\/delete.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/delete.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client","server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"JWT":[]}]}},"\/account\/email":{"patch":{"summary":"Update Account Email","operationId":"accountUpdateEmail","consumes":["application\/json"],"produces":["application\/json"],"tags":["account"],"description":"Update currently logged in user account email address. After changing user address, user confirmation status is being reset and a new confirmation mail is sent. For security measures, user password is required to complete this request.\nThis endpoint can also be used to convert an anonymous account to a normal one, by passing an email address and a new password.","responses":{"200":{"description":"User","schema":{"$ref":"#\/definitions\/user"}}},"x-appwrite":{"method":"updateEmail","weight":50,"cookies":false,"type":"","demo":"account\/update-email.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/update-email.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client","server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"JWT":[]}],"parameters":[{"name":"payload","in":"body","schema":{"type":"object","properties":{"email":{"type":"string","description":"User email.","default":null,"x-example":"email@example.com"},"password":{"type":"string","description":"User password. Must be between 6 to 32 chars.","default":null,"x-example":"password"}},"required":["email","password"]}}]}},"\/account\/jwt":{"post":{"summary":"Create Account JWT","operationId":"accountCreateJWT","consumes":["application\/json"],"produces":["application\/json"],"tags":["account"],"description":"Use this endpoint to create a JSON Web Token. You can use the resulting JWT to authenticate on behalf of the current user when working with the Appwrite server-side API and SDKs. The JWT secret is valid for 15 minutes from its creation and will be invalid if the user will logout in that time frame.","responses":{"201":{"description":"JWT","schema":{"$ref":"#\/definitions\/jwt"}}},"x-appwrite":{"method":"createJWT","weight":43,"cookies":false,"type":"","demo":"account\/create-j-w-t.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/create-jwt.md","rate-limit":10,"rate-time":3600,"rate-key":"url:{url},userId:{param-userId}","scope":"account","platforms":["client"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[]}]}},"\/account\/logs":{"get":{"summary":"Get Account Logs","operationId":"accountGetLogs","consumes":["application\/json"],"produces":["application\/json"],"tags":["account"],"description":"Get currently logged in user list of latest security activity logs. Each log returns user IP address, location and date and time of log.","responses":{"200":{"description":"Logs List","schema":{"$ref":"#\/definitions\/logList"}}},"x-appwrite":{"method":"getLogs","weight":47,"cookies":false,"type":"","demo":"account\/get-logs.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/get-logs.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client","server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"JWT":[]}]}},"\/account\/name":{"patch":{"summary":"Update Account Name","operationId":"accountUpdateName","consumes":["application\/json"],"produces":["application\/json"],"tags":["account"],"description":"Update currently logged in user account name.","responses":{"200":{"description":"User","schema":{"$ref":"#\/definitions\/user"}}},"x-appwrite":{"method":"updateName","weight":48,"cookies":false,"type":"","demo":"account\/update-name.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/update-name.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client","server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"JWT":[]}],"parameters":[{"name":"payload","in":"body","schema":{"type":"object","properties":{"name":{"type":"string","description":"User name. Max length: 128 chars.","default":null,"x-example":"[NAME]"}},"required":["name"]}}]}},"\/account\/password":{"patch":{"summary":"Update Account Password","operationId":"accountUpdatePassword","consumes":["application\/json"],"produces":["application\/json"],"tags":["account"],"description":"Update currently logged in user password. For validation, user is required to pass in the new password, and the old password. For users created with OAuth and Team Invites, oldPassword is optional.","responses":{"200":{"description":"User","schema":{"$ref":"#\/definitions\/user"}}},"x-appwrite":{"method":"updatePassword","weight":49,"cookies":false,"type":"","demo":"account\/update-password.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/update-password.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client","server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"JWT":[]}],"parameters":[{"name":"payload","in":"body","schema":{"type":"object","properties":{"password":{"type":"string","description":"New user password. Must be between 6 to 32 chars.","default":null,"x-example":"password"},"oldPassword":{"type":"string","description":"Old user password. Must be between 6 to 32 chars.","default":"","x-example":"password"}},"required":["password"]}}]}},"\/account\/prefs":{"get":{"summary":"Get Account Preferences","operationId":"accountGetPrefs","consumes":["application\/json"],"produces":["application\/json"],"tags":["account"],"description":"Get currently logged in user preferences as a key-value object.","responses":{"200":{"description":"Preferences","schema":{"$ref":"#\/definitions\/preferences"}}},"x-appwrite":{"method":"getPrefs","weight":45,"cookies":false,"type":"","demo":"account\/get-prefs.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/get-prefs.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client","server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"JWT":[]}]},"patch":{"summary":"Update Account Preferences","operationId":"accountUpdatePrefs","consumes":["application\/json"],"produces":["application\/json"],"tags":["account"],"description":"Update currently logged in user account preferences. You can pass only the specific settings you wish to update.","responses":{"200":{"description":"User","schema":{"$ref":"#\/definitions\/user"}}},"x-appwrite":{"method":"updatePrefs","weight":51,"cookies":false,"type":"","demo":"account\/update-prefs.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/update-prefs.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client","server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"JWT":[]}],"parameters":[{"name":"payload","in":"body","schema":{"type":"object","properties":{"prefs":{"type":"object","description":"Prefs key-value JSON object.","default":null,"x-example":"{}"}},"required":["prefs"]}}]}},"\/account\/recovery":{"post":{"summary":"Create Password Recovery","operationId":"accountCreateRecovery","consumes":["application\/json"],"produces":["application\/json"],"tags":["account"],"description":"Sends the user an email with a temporary secret key for password reset. When the user clicks the confirmation link he is redirected back to your app password reset URL with the secret key and email address values attached to the URL query string. Use the query string params to submit a request to the [PUT \/account\/recovery](\/docs\/client\/account#accountUpdateRecovery) endpoint to complete the process. The verification link sent to the user's email address is valid for 1 hour.","responses":{"201":{"description":"Token","schema":{"$ref":"#\/definitions\/token"}}},"x-appwrite":{"method":"createRecovery","weight":55,"cookies":false,"type":"","demo":"account\/create-recovery.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/create-recovery.md","rate-limit":10,"rate-time":3600,"rate-key":"url:{url},email:{param-email}","scope":"public","platforms":["client","server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"JWT":[]}],"parameters":[{"name":"payload","in":"body","schema":{"type":"object","properties":{"email":{"type":"string","description":"User email.","default":null,"x-example":"email@example.com"},"url":{"type":"string","description":"URL to redirect the user back to your app from the recovery email. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https:\/\/cheatsheetseries.owasp.org\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.","default":null,"x-example":"https:\/\/example.com"}},"required":["email","url"]}}]},"put":{"summary":"Complete Password Recovery","operationId":"accountUpdateRecovery","consumes":["application\/json"],"produces":["application\/json"],"tags":["account"],"description":"Use this endpoint to complete the user account password reset. Both the **userId** and **secret** arguments will be passed as query parameters to the redirect URL you have provided when sending your request to the [POST \/account\/recovery](\/docs\/client\/account#accountCreateRecovery) endpoint.\n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.","responses":{"200":{"description":"Token","schema":{"$ref":"#\/definitions\/token"}}},"x-appwrite":{"method":"updateRecovery","weight":56,"cookies":false,"type":"","demo":"account\/update-recovery.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/update-recovery.md","rate-limit":10,"rate-time":3600,"rate-key":"url:{url},userId:{param-userId}","scope":"public","platforms":["client","server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"JWT":[]}],"parameters":[{"name":"payload","in":"body","schema":{"type":"object","properties":{"userId":{"type":"string","description":"User account UID address.","default":null,"x-example":null},"secret":{"type":"string","description":"Valid reset token.","default":null,"x-example":"[SECRET]"},"password":{"type":"string","description":"New password. Must be between 6 to 32 chars.","default":null,"x-example":"password"},"passwordAgain":{"type":"string","description":"New password again. Must be between 6 to 32 chars.","default":null,"x-example":"password"}},"required":["userId","secret","password","passwordAgain"]}}]}},"\/account\/sessions":{"get":{"summary":"Get Account Sessions","operationId":"accountGetSessions","consumes":["application\/json"],"produces":["application\/json"],"tags":["account"],"description":"Get currently logged in user list of active sessions across different devices.","responses":{"200":{"description":"Sessions List","schema":{"$ref":"#\/definitions\/sessionList"}}},"x-appwrite":{"method":"getSessions","weight":46,"cookies":false,"type":"","demo":"account\/get-sessions.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/get-sessions.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client","server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"JWT":[]}]},"post":{"summary":"Create Account Session","operationId":"accountCreateSession","consumes":["application\/json"],"produces":["application\/json"],"tags":["account"],"description":"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.","responses":{"201":{"description":"Session","schema":{"$ref":"#\/definitions\/session"}}},"x-appwrite":{"method":"createSession","weight":37,"cookies":false,"type":"","demo":"account\/create-session.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/create-session.md","rate-limit":10,"rate-time":3600,"rate-key":"url:{url},email:{param-email}","scope":"public","platforms":["client"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[]}],"parameters":[{"name":"payload","in":"body","schema":{"type":"object","properties":{"email":{"type":"string","description":"User email.","default":null,"x-example":"email@example.com"},"password":{"type":"string","description":"User password. Must be between 6 to 32 chars.","default":null,"x-example":"password"}},"required":["email","password"]}}]},"delete":{"summary":"Delete All Account Sessions","operationId":"accountDeleteSessions","consumes":["application\/json"],"produces":[],"tags":["account"],"description":"Delete all sessions from the user account and remove any sessions cookies from the end client.","responses":{"204":{"description":"No content"}},"x-appwrite":{"method":"deleteSessions","weight":54,"cookies":false,"type":"","demo":"account\/delete-sessions.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/delete-sessions.md","rate-limit":100,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client","server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"JWT":[]}]}},"\/account\/sessions\/anonymous":{"post":{"summary":"Create Anonymous Session","operationId":"accountCreateAnonymousSession","consumes":["application\/json"],"produces":["application\/json"],"tags":["account"],"description":"Use this endpoint to allow a new user to register an anonymous account in your project. This route will also create a new session for the user. To allow the new user to convert an anonymous account to a normal account account, you need to update its [email and password](\/docs\/client\/account#accountUpdateEmail).","responses":{"201":{"description":"Session","schema":{"$ref":"#\/definitions\/session"}}},"x-appwrite":{"method":"createAnonymousSession","weight":42,"cookies":false,"type":"","demo":"account\/create-anonymous-session.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/create-session-anonymous.md","rate-limit":50,"rate-time":3600,"rate-key":"ip:{ip}","scope":"public","platforms":["client"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[]}]}},"\/account\/sessions\/oauth2\/{provider}":{"get":{"summary":"Create Account Session with OAuth2","operationId":"accountCreateOAuth2Session","consumes":["application\/json"],"produces":["text\/html"],"tags":["account"],"description":"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.","responses":{"301":{"description":"No content"}},"x-appwrite":{"method":"createOAuth2Session","weight":38,"cookies":false,"type":"webAuth","demo":"account\/create-o-auth2session.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/create-session-oauth2.md","rate-limit":50,"rate-time":3600,"rate-key":"ip:{ip}","scope":"public","platforms":["client"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[]}],"parameters":[{"name":"provider","description":"OAuth2 Provider. Currently, supported providers are: amazon, apple, bitbucket, bitly, box, discord, dropbox, facebook, github, gitlab, google, linkedin, microsoft, paypal, paypalSandbox, salesforce, slack, spotify, tradeshift, tradeshiftBox, twitch, vk, yahoo, yandex, wordpress.","required":true,"type":"string","x-example":"amazon","in":"path"},{"name":"success","description":"URL to redirect back to your app after a successful login attempt. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https:\/\/cheatsheetseries.owasp.org\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.","required":false,"type":"string","format":"url","x-example":"https:\/\/example.com","default":"https:\/\/appwrite.io\/auth\/oauth2\/success","in":"query"},{"name":"failure","description":"URL to redirect back to your app after a failed login attempt. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https:\/\/cheatsheetseries.owasp.org\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.","required":false,"type":"string","format":"url","x-example":"https:\/\/example.com","default":"https:\/\/appwrite.io\/auth\/oauth2\/failure","in":"query"},{"name":"scopes","description":"A list of custom OAuth2 scopes. Check each provider internal docs for a list of supported scopes.","required":false,"type":"array","collectionFormat":"multi","items":{"type":"string"},"default":[],"in":"query"}]}},"\/account\/sessions\/{sessionId}":{"delete":{"summary":"Delete Account Session","operationId":"accountDeleteSession","consumes":["application\/json"],"produces":[],"tags":["account"],"description":"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.","responses":{"204":{"description":"No content"}},"x-appwrite":{"method":"deleteSession","weight":53,"cookies":false,"type":"","demo":"account\/delete-session.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/delete-session.md","rate-limit":100,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client","server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"JWT":[]}],"parameters":[{"name":"sessionId","description":"Session unique ID. Use the string 'current' to delete the current device session.","required":true,"type":"string","in":"path"}]}},"\/account\/verification":{"post":{"summary":"Create Email Verification","operationId":"accountCreateVerification","consumes":["application\/json"],"produces":["application\/json"],"tags":["account"],"description":"Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL 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#accountUpdateVerification). The verification link sent to the user's email address is valid for 7 days.\n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.\n","responses":{"201":{"description":"Token","schema":{"$ref":"#\/definitions\/token"}}},"x-appwrite":{"method":"createVerification","weight":57,"cookies":false,"type":"","demo":"account\/create-verification.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/create-verification.md","rate-limit":10,"rate-time":3600,"rate-key":"url:{url},email:{param-email}","scope":"account","platforms":["client","server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"JWT":[]}],"parameters":[{"name":"payload","in":"body","schema":{"type":"object","properties":{"url":{"type":"string","description":"URL to redirect the user back to your app from the verification email. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https:\/\/cheatsheetseries.owasp.org\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.","default":null,"x-example":"https:\/\/example.com"}},"required":["url"]}}]},"put":{"summary":"Complete Email Verification","operationId":"accountUpdateVerification","consumes":["application\/json"],"produces":["application\/json"],"tags":["account"],"description":"Use this endpoint to complete the user email verification process. Use both the **userId** and **secret** parameters that were attached to your app URL to verify the user email ownership. If confirmed this route will return a 200 status code.","responses":{"200":{"description":"Token","schema":{"$ref":"#\/definitions\/token"}}},"x-appwrite":{"method":"updateVerification","weight":58,"cookies":false,"type":"","demo":"account\/update-verification.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/update-verification.md","rate-limit":10,"rate-time":3600,"rate-key":"url:{url},userId:{param-userId}","scope":"public","platforms":["client","server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"JWT":[]}],"parameters":[{"name":"payload","in":"body","schema":{"type":"object","properties":{"userId":{"type":"string","description":"User unique ID.","default":null,"x-example":null},"secret":{"type":"string","description":"Valid verification token.","default":null,"x-example":"[SECRET]"}},"required":["userId","secret"]}}]}},"\/avatars\/browsers\/{code}":{"get":{"summary":"Get Browser Icon","operationId":"avatarsGetBrowser","consumes":["application\/json"],"produces":["image\/png"],"tags":["avatars"],"description":"You can use this endpoint to show different browser icons to your users. The code argument receives the browser code as it appears in your user \/account\/sessions endpoint. Use width, height and quality arguments to change the output settings.","responses":{"200":{"description":"Image","schema":{"type":"file"}}},"x-appwrite":{"method":"getBrowser","weight":60,"cookies":false,"type":"location","demo":"avatars\/get-browser.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/avatars\/get-browser.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"avatars.read","platforms":["client","server","server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[],"JWT":[]}],"parameters":[{"name":"code","description":"Browser Code.","required":true,"type":"string","x-example":"aa","in":"path"},{"name":"width","description":"Image width. Pass an integer between 0 to 2000. Defaults to 100.","required":false,"type":"integer","format":"int32","x-example":0,"default":100,"in":"query"},{"name":"height","description":"Image height. Pass an integer between 0 to 2000. Defaults to 100.","required":false,"type":"integer","format":"int32","x-example":0,"default":100,"in":"query"},{"name":"quality","description":"Image quality. Pass an integer between 0 to 100. Defaults to 100.","required":false,"type":"integer","format":"int32","x-example":0,"default":100,"in":"query"}]}},"\/avatars\/credit-cards\/{code}":{"get":{"summary":"Get Credit Card Icon","operationId":"avatarsGetCreditCard","consumes":["application\/json"],"produces":["image\/png"],"tags":["avatars"],"description":"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.","responses":{"200":{"description":"Image","schema":{"type":"file"}}},"x-appwrite":{"method":"getCreditCard","weight":59,"cookies":false,"type":"location","demo":"avatars\/get-credit-card.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/avatars\/get-credit-card.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"avatars.read","platforms":["client","server","server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[],"JWT":[]}],"parameters":[{"name":"code","description":"Credit Card Code. Possible values: amex, argencard, cabal, censosud, diners, discover, elo, hipercard, jcb, mastercard, naranja, targeta-shopping, union-china-pay, visa, mir, maestro.","required":true,"type":"string","x-example":"amex","in":"path"},{"name":"width","description":"Image width. Pass an integer between 0 to 2000. Defaults to 100.","required":false,"type":"integer","format":"int32","x-example":0,"default":100,"in":"query"},{"name":"height","description":"Image height. Pass an integer between 0 to 2000. Defaults to 100.","required":false,"type":"integer","format":"int32","x-example":0,"default":100,"in":"query"},{"name":"quality","description":"Image quality. Pass an integer between 0 to 100. Defaults to 100.","required":false,"type":"integer","format":"int32","x-example":0,"default":100,"in":"query"}]}},"\/avatars\/favicon":{"get":{"summary":"Get Favicon","operationId":"avatarsGetFavicon","consumes":["application\/json"],"produces":["image\/*"],"tags":["avatars"],"description":"Use this endpoint to fetch the favorite icon (AKA favicon) of any remote website URL.\n","responses":{"200":{"description":"Image","schema":{"type":"file"}}},"x-appwrite":{"method":"getFavicon","weight":63,"cookies":false,"type":"location","demo":"avatars\/get-favicon.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/avatars\/get-favicon.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"avatars.read","platforms":["client","server","server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[],"JWT":[]}],"parameters":[{"name":"url","description":"Website URL which you want to fetch the favicon from.","required":true,"type":"string","format":"url","x-example":"https:\/\/example.com","in":"query"}]}},"\/avatars\/flags\/{code}":{"get":{"summary":"Get Country Flag","operationId":"avatarsGetFlag","consumes":["application\/json"],"produces":["image\/png"],"tags":["avatars"],"description":"You can use this endpoint to show different country flags icons to your users. The code argument receives the 2 letter country code. Use width, height and quality arguments to change the output settings.","responses":{"200":{"description":"Image","schema":{"type":"file"}}},"x-appwrite":{"method":"getFlag","weight":61,"cookies":false,"type":"location","demo":"avatars\/get-flag.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/avatars\/get-flag.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"avatars.read","platforms":["client","server","server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[],"JWT":[]}],"parameters":[{"name":"code","description":"Country Code. ISO Alpha-2 country code format.","required":true,"type":"string","x-example":"af","in":"path"},{"name":"width","description":"Image width. Pass an integer between 0 to 2000. Defaults to 100.","required":false,"type":"integer","format":"int32","x-example":0,"default":100,"in":"query"},{"name":"height","description":"Image height. Pass an integer between 0 to 2000. Defaults to 100.","required":false,"type":"integer","format":"int32","x-example":0,"default":100,"in":"query"},{"name":"quality","description":"Image quality. Pass an integer between 0 to 100. Defaults to 100.","required":false,"type":"integer","format":"int32","x-example":0,"default":100,"in":"query"}]}},"\/avatars\/image":{"get":{"summary":"Get Image from URL","operationId":"avatarsGetImage","consumes":["application\/json"],"produces":["image\/*"],"tags":["avatars"],"description":"Use this endpoint to fetch a remote image URL and crop it to any image size you want. This endpoint is very useful if you need to crop and display remote images in your app or in case you want to make sure a 3rd party image is properly served using a TLS protocol.","responses":{"200":{"description":"Image","schema":{"type":"file"}}},"x-appwrite":{"method":"getImage","weight":62,"cookies":false,"type":"location","demo":"avatars\/get-image.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/avatars\/get-image.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"avatars.read","platforms":["client","server","server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[],"JWT":[]}],"parameters":[{"name":"url","description":"Image URL which you want to crop.","required":true,"type":"string","format":"url","x-example":"https:\/\/example.com","in":"query"},{"name":"width","description":"Resize preview image width, Pass an integer between 0 to 2000.","required":false,"type":"integer","format":"int32","x-example":0,"default":400,"in":"query"},{"name":"height","description":"Resize preview image height, Pass an integer between 0 to 2000.","required":false,"type":"integer","format":"int32","x-example":0,"default":400,"in":"query"}]}},"\/avatars\/initials":{"get":{"summary":"Get User Initials","operationId":"avatarsGetInitials","consumes":["application\/json"],"produces":["image\/png"],"tags":["avatars"],"description":"Use this endpoint to show your user initials avatar icon on your website or app. By default, this route will try to print your logged-in user name or email initials. You can also overwrite the user name if you pass the 'name' parameter. If no name is given and no user is logged, an empty avatar will be returned.\n\nYou can use the color and background params to change the avatar colors. By default, a random theme will be selected. The random theme will persist for the user's initials when reloading the same theme will always return for the same initials.","responses":{"200":{"description":"Image","schema":{"type":"file"}}},"x-appwrite":{"method":"getInitials","weight":65,"cookies":false,"type":"location","demo":"avatars\/get-initials.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/avatars\/get-initials.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"avatars.read","platforms":["client","server","server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[],"JWT":[]}],"parameters":[{"name":"name","description":"Full Name. When empty, current user name or email will be used. Max length: 128 chars.","required":false,"type":"string","x-example":"[NAME]","default":"","in":"query"},{"name":"width","description":"Image width. Pass an integer between 0 to 2000. Defaults to 100.","required":false,"type":"integer","format":"int32","x-example":0,"default":500,"in":"query"},{"name":"height","description":"Image height. Pass an integer between 0 to 2000. Defaults to 100.","required":false,"type":"integer","format":"int32","x-example":0,"default":500,"in":"query"},{"name":"color","description":"Changes text color. By default a random color will be picked and stay will persistent to the given name.","required":false,"type":"string","default":"","in":"query"},{"name":"background","description":"Changes background color. By default a random color will be picked and stay will persistent to the given name.","required":false,"type":"string","default":"","in":"query"}]}},"\/avatars\/qr":{"get":{"summary":"Get QR Code","operationId":"avatarsGetQR","consumes":["application\/json"],"produces":["image\/png"],"tags":["avatars"],"description":"Converts a given plain text to a QR code image. You can use the query parameters to change the size and style of the resulting image.","responses":{"200":{"description":"Image","schema":{"type":"file"}}},"x-appwrite":{"method":"getQR","weight":64,"cookies":false,"type":"location","demo":"avatars\/get-q-r.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/avatars\/get-qr.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"avatars.read","platforms":["client","server","server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[],"JWT":[]}],"parameters":[{"name":"text","description":"Plain text to be converted to QR code image.","required":true,"type":"string","x-example":"[TEXT]","in":"query"},{"name":"size","description":"QR code size. Pass an integer between 0 to 1000. Defaults to 400.","required":false,"type":"integer","format":"int32","x-example":0,"default":400,"in":"query"},{"name":"margin","description":"Margin from edge. Pass an integer between 0 to 10. Defaults to 1.","required":false,"type":"integer","format":"int32","x-example":0,"default":1,"in":"query"},{"name":"download","description":"Return resulting image with 'Content-Disposition: attachment ' headers for the browser to start downloading it. Pass 0 for no header, or 1 for otherwise. Default value is set to 0.","required":false,"type":"boolean","x-example":false,"default":false,"in":"query"}]}},"\/database\/collections":{"get":{"summary":"List Collections","operationId":"databaseListCollections","consumes":["application\/json"],"produces":["application\/json"],"tags":["database"],"description":"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).","responses":{"200":{"description":"Collections List","schema":{"$ref":"#\/definitions\/collectionList"}}},"x-appwrite":{"method":"listCollections","weight":67,"cookies":false,"type":"","demo":"database\/list-collections.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/database\/list-collections.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"collections.read","platforms":["server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"search","description":"Search term to filter your list results. Max length: 256 chars.","required":false,"type":"string","x-example":"[SEARCH]","default":"","in":"query"},{"name":"limit","description":"Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.","required":false,"type":"integer","format":"int32","x-example":0,"default":25,"in":"query"},{"name":"offset","description":"Results offset. The default value is 0. Use this param to manage pagination.","required":false,"type":"integer","format":"int32","x-example":0,"default":0,"in":"query"},{"name":"orderType","description":"Order result by ASC or DESC order.","required":false,"type":"string","x-example":"ASC","default":"ASC","in":"query"}]},"post":{"summary":"Create Collection","operationId":"databaseCreateCollection","consumes":["application\/json"],"produces":["application\/json"],"tags":["database"],"description":"Create a new Collection.","responses":{"201":{"description":"Collection","schema":{"$ref":"#\/definitions\/collection"}}},"x-appwrite":{"method":"createCollection","weight":66,"cookies":false,"type":"","demo":"database\/create-collection.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/database\/create-collection.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"collections.write","platforms":["server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"payload","in":"body","schema":{"type":"object","properties":{"name":{"type":"string","description":"Collection name. Max length: 128 chars.","default":null,"x-example":"[NAME]"},"read":{"type":"array","description":"An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions](\/docs\/permissions) and get a full list of available permissions.","default":null,"x-example":null,"items":{"type":"string"}},"write":{"type":"array","description":"An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](\/docs\/permissions) and get a full list of available permissions.","default":null,"x-example":null,"items":{"type":"string"}},"rules":{"type":"array","description":"Array of [rule objects](\/docs\/rules). Each rule define a collection field name, data type and validation.","default":null,"x-example":null,"items":{"type":"string"}}},"required":["name","read","write","rules"]}}]}},"\/database\/collections\/{collectionId}":{"get":{"summary":"Get Collection","operationId":"databaseGetCollection","consumes":["application\/json"],"produces":["application\/json"],"tags":["database"],"description":"Get a collection by its unique ID. This endpoint response returns a JSON object with the collection metadata.","responses":{"200":{"description":"Collection","schema":{"$ref":"#\/definitions\/collection"}}},"x-appwrite":{"method":"getCollection","weight":68,"cookies":false,"type":"","demo":"database\/get-collection.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/database\/get-collection.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"collections.read","platforms":["server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"collectionId","description":"Collection unique ID.","required":true,"type":"string","x-example":"[COLLECTION_ID]","in":"path"}]},"put":{"summary":"Update Collection","operationId":"databaseUpdateCollection","consumes":["application\/json"],"produces":["application\/json"],"tags":["database"],"description":"Update a collection by its unique ID.","responses":{"200":{"description":"Collection","schema":{"$ref":"#\/definitions\/collection"}}},"x-appwrite":{"method":"updateCollection","weight":69,"cookies":false,"type":"","demo":"database\/update-collection.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/database\/update-collection.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"collections.write","platforms":["server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"collectionId","description":"Collection unique ID.","required":true,"type":"string","x-example":"[COLLECTION_ID]","in":"path"},{"name":"payload","in":"body","schema":{"type":"object","properties":{"name":{"type":"string","description":"Collection name. Max length: 128 chars.","default":null,"x-example":"[NAME]"},"read":{"type":"array","description":"An array of strings with read permissions. By default inherits the existing read permissions. [learn more about permissions](\/docs\/permissions) and get a full list of available permissions.","default":null,"x-example":null,"items":{"type":"string"}},"write":{"type":"array","description":"An array of strings with write permissions. By default inherits the existing write permissions. [learn more about permissions](\/docs\/permissions) and get a full list of available permissions.","default":null,"x-example":null,"items":{"type":"string"}},"rules":{"type":"array","description":"Array of [rule objects](\/docs\/rules). Each rule define a collection field name, data type and validation.","default":[],"x-example":null,"items":{"type":"string"}}},"required":["name"]}}]},"delete":{"summary":"Delete Collection","operationId":"databaseDeleteCollection","consumes":["application\/json"],"produces":[],"tags":["database"],"description":"Delete a collection by its unique ID. Only users with write permissions have access to delete this resource.","responses":{"204":{"description":"No content"}},"x-appwrite":{"method":"deleteCollection","weight":70,"cookies":false,"type":"","demo":"database\/delete-collection.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/database\/delete-collection.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"collections.write","platforms":["server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"collectionId","description":"Collection unique ID.","required":true,"type":"string","x-example":"[COLLECTION_ID]","in":"path"}]}},"\/database\/collections\/{collectionId}\/documents":{"get":{"summary":"List Documents","operationId":"databaseListDocuments","consumes":["application\/json"],"produces":["application\/json"],"tags":["database"],"description":"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).","responses":{"200":{"description":"Documents List","schema":{"$ref":"#\/definitions\/documentList"}}},"x-appwrite":{"method":"listDocuments","weight":72,"cookies":false,"type":"","demo":"database\/list-documents.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/database\/list-documents.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"documents.read","platforms":["client","server","server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[],"JWT":[]}],"parameters":[{"name":"collectionId","description":"Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](\/docs\/server\/database#createCollection).","required":true,"type":"string","x-example":"[COLLECTION_ID]","in":"path"},{"name":"filters","description":"Array of filter strings. Each filter is constructed from a key name, comparison operator (=, !=, >, <, <=, >=) and a value. You can also use a dot (.) separator in attribute names to filter by child document attributes. Examples: 'name=John Doe' or 'category.$id>=5bed2d152c362'.","required":false,"type":"array","collectionFormat":"multi","items":{"type":"string"},"default":[],"in":"query"},{"name":"limit","description":"Maximum number of documents to return in response. Use this value to manage pagination. By default will return maximum 25 results. Maximum of 100 results allowed per request.","required":false,"type":"integer","format":"int32","x-example":0,"default":25,"in":"query"},{"name":"offset","description":"Offset value. The default value is 0. Use this param to manage pagination.","required":false,"type":"integer","format":"int32","x-example":0,"default":0,"in":"query"},{"name":"orderField","description":"Document field that results will be sorted by.","required":false,"type":"string","x-example":"[ORDER_FIELD]","default":"","in":"query"},{"name":"orderType","description":"Order direction. Possible values are DESC for descending order, or ASC for ascending order.","required":false,"type":"string","x-example":"DESC","default":"ASC","in":"query"},{"name":"orderCast","description":"Order field type casting. Possible values are int, string, date, time or datetime. The database will attempt to cast the order field to the value you pass here. The default value is a string.","required":false,"type":"string","x-example":"int","default":"string","in":"query"},{"name":"search","description":"Search query. Enter any free text search. The database will try to find a match against all document attributes and children. Max length: 256 chars.","required":false,"type":"string","x-example":"[SEARCH]","default":"","in":"query"}]},"post":{"summary":"Create Document","operationId":"databaseCreateDocument","consumes":["application\/json"],"produces":["application\/json"],"tags":["database"],"description":"Create a new Document. Before using this route, you should create a new collection resource using either a [server integration](\/docs\/server\/database#databaseCreateCollection) API or directly from your database console.","responses":{"201":{"description":"Document","schema":{"$ref":"#\/definitions\/document"}}},"x-appwrite":{"method":"createDocument","weight":71,"cookies":false,"type":"","demo":"database\/create-document.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/database\/create-document.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"documents.write","platforms":["client","server","server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[],"JWT":[]}],"parameters":[{"name":"collectionId","description":"Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](\/docs\/server\/database#createCollection).","required":true,"type":"string","x-example":"[COLLECTION_ID]","in":"path"},{"name":"payload","in":"body","schema":{"type":"object","properties":{"data":{"type":"object","description":"Document data as JSON object.","default":null,"x-example":"{}"},"read":{"type":"array","description":"An array of strings with read permissions. By default only the current user is granted with read permissions. [learn more about permissions](\/docs\/permissions) and get a full list of available permissions.","default":null,"x-example":null,"items":{"type":"string"}},"write":{"type":"array","description":"An array of strings with write permissions. By default only the current user is granted with write permissions. [learn more about permissions](\/docs\/permissions) and get a full list of available permissions.","default":null,"x-example":null,"items":{"type":"string"}},"parentDocument":{"type":"string","description":"Parent document unique ID. Use when you want your new document to be a child of a parent document.","default":"","x-example":"[PARENT_DOCUMENT]"},"parentProperty":{"type":"string","description":"Parent document property name. Use when you want your new document to be a child of a parent document.","default":"","x-example":null},"parentPropertyType":{"type":"string","description":"Parent document property connection type. You can set this value to **assign**, **append** or **prepend**, default value is assign. Use when you want your new document to be a child of a parent document.","default":"assign","x-example":"assign"}},"required":["data"]}}]}},"\/database\/collections\/{collectionId}\/documents\/{documentId}":{"get":{"summary":"Get Document","operationId":"databaseGetDocument","consumes":["application\/json"],"produces":["application\/json"],"tags":["database"],"description":"Get a document by its unique ID. This endpoint response returns a JSON object with the document data.","responses":{"200":{"description":"Document","schema":{"$ref":"#\/definitions\/document"}}},"x-appwrite":{"method":"getDocument","weight":73,"cookies":false,"type":"","demo":"database\/get-document.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/database\/get-document.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"documents.read","platforms":["client","server","server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[],"JWT":[]}],"parameters":[{"name":"collectionId","description":"Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](\/docs\/server\/database#createCollection).","required":true,"type":"string","x-example":"[COLLECTION_ID]","in":"path"},{"name":"documentId","description":"Document unique ID.","required":true,"type":"string","x-example":"[DOCUMENT_ID]","in":"path"}]},"patch":{"summary":"Update Document","operationId":"databaseUpdateDocument","consumes":["application\/json"],"produces":["application\/json"],"tags":["database"],"description":"Update a document by its unique ID. Using the patch method you can pass only specific fields that will get updated.","responses":{"200":{"description":"Document","schema":{"$ref":"#\/definitions\/document"}}},"x-appwrite":{"method":"updateDocument","weight":74,"cookies":false,"type":"","demo":"database\/update-document.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/database\/update-document.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"documents.write","platforms":["client","server","server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[],"JWT":[]}],"parameters":[{"name":"collectionId","description":"Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](\/docs\/server\/database#createCollection).","required":true,"type":"string","x-example":"[COLLECTION_ID]","in":"path"},{"name":"documentId","description":"Document unique ID.","required":true,"type":"string","x-example":"[DOCUMENT_ID]","in":"path"},{"name":"payload","in":"body","schema":{"type":"object","properties":{"data":{"type":"object","description":"Document data as JSON object.","default":null,"x-example":"{}"},"read":{"type":"array","description":"An array of strings with read permissions. By default inherits the existing read permissions. [learn more about permissions](\/docs\/permissions) and get a full list of available permissions.","default":null,"x-example":null,"items":{"type":"string"}},"write":{"type":"array","description":"An array of strings with write permissions. By default inherits the existing write permissions. [learn more about permissions](\/docs\/permissions) and get a full list of available permissions.","default":null,"x-example":null,"items":{"type":"string"}}},"required":["data"]}}]},"delete":{"summary":"Delete Document","operationId":"databaseDeleteDocument","consumes":["application\/json"],"produces":[],"tags":["database"],"description":"Delete a document by its unique ID. This endpoint deletes only the parent documents, its attributes and relations to other documents. Child documents **will not** be deleted.","responses":{"204":{"description":"No content"}},"x-appwrite":{"method":"deleteDocument","weight":75,"cookies":false,"type":"","demo":"database\/delete-document.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/database\/delete-document.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"documents.write","platforms":["client","server","server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[],"JWT":[]}],"parameters":[{"name":"collectionId","description":"Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](\/docs\/server\/database#createCollection).","required":true,"type":"string","x-example":"[COLLECTION_ID]","in":"path"},{"name":"documentId","description":"Document unique ID.","required":true,"type":"string","x-example":"[DOCUMENT_ID]","in":"path"}]}},"\/functions":{"get":{"summary":"List Functions","operationId":"functionsList","consumes":["application\/json"],"produces":["application\/json"],"tags":["functions"],"description":"Get a list of all the project's functions. You can use the query params to filter your results.","responses":{"200":{"description":"Functions List","schema":{"$ref":"#\/definitions\/functionList"}}},"x-appwrite":{"method":"list","weight":162,"cookies":false,"type":"","demo":"functions\/list.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/functions\/list-functions.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"functions.read","platforms":["server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"search","description":"Search term to filter your list results. Max length: 256 chars.","required":false,"type":"string","x-example":"[SEARCH]","default":"","in":"query"},{"name":"limit","description":"Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.","required":false,"type":"integer","format":"int32","x-example":0,"default":25,"in":"query"},{"name":"offset","description":"Results offset. The default value is 0. Use this param to manage pagination.","required":false,"type":"integer","format":"int32","x-example":0,"default":0,"in":"query"},{"name":"orderType","description":"Order result by ASC or DESC order.","required":false,"type":"string","x-example":"ASC","default":"ASC","in":"query"}]},"post":{"summary":"Create Function","operationId":"functionsCreate","consumes":["application\/json"],"produces":["application\/json"],"tags":["functions"],"description":"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.","responses":{"201":{"description":"Function","schema":{"$ref":"#\/definitions\/function"}}},"x-appwrite":{"method":"create","weight":161,"cookies":false,"type":"","demo":"functions\/create.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/functions\/create-function.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"functions.write","platforms":["server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"payload","in":"body","schema":{"type":"object","properties":{"name":{"type":"string","description":"Function name. Max length: 128 chars.","default":null,"x-example":"[NAME]"},"execute":{"type":"array","description":"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.","default":null,"x-example":null,"items":{"type":"string"}},"env":{"type":"string","description":"Execution enviornment.","default":null,"x-example":"dotnet-3.1"},"vars":{"type":"object","description":"Key-value JSON object.","default":[],"x-example":"{}"},"events":{"type":"array","description":"Events list.","default":[],"x-example":null,"items":{"type":"string"}},"schedule":{"type":"string","description":"Schedule CRON syntax.","default":"","x-example":null},"timeout":{"type":"integer","description":"Function maximum execution time in seconds.","default":15,"x-example":1}},"required":["name","execute","env"]}}]}},"\/functions\/{functionId}":{"get":{"summary":"Get Function","operationId":"functionsGet","consumes":["application\/json"],"produces":["application\/json"],"tags":["functions"],"description":"Get a function by its unique ID.","responses":{"200":{"description":"Function","schema":{"$ref":"#\/definitions\/function"}}},"x-appwrite":{"method":"get","weight":163,"cookies":false,"type":"","demo":"functions\/get.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/functions\/get-function.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"functions.read","platforms":["server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"functionId","description":"Function unique ID.","required":true,"type":"string","x-example":"[FUNCTION_ID]","in":"path"}]},"put":{"summary":"Update Function","operationId":"functionsUpdate","consumes":["application\/json"],"produces":["application\/json"],"tags":["functions"],"description":"Update function by its unique ID.","responses":{"200":{"description":"Function","schema":{"$ref":"#\/definitions\/function"}}},"x-appwrite":{"method":"update","weight":165,"cookies":false,"type":"","demo":"functions\/update.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/functions\/update-function.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"functions.write","platforms":["server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"functionId","description":"Function unique ID.","required":true,"type":"string","x-example":"[FUNCTION_ID]","in":"path"},{"name":"payload","in":"body","schema":{"type":"object","properties":{"name":{"type":"string","description":"Function name. Max length: 128 chars.","default":null,"x-example":"[NAME]"},"execute":{"type":"array","description":"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.","default":null,"x-example":null,"items":{"type":"string"}},"vars":{"type":"object","description":"Key-value JSON object.","default":[],"x-example":"{}"},"events":{"type":"array","description":"Events list.","default":[],"x-example":null,"items":{"type":"string"}},"schedule":{"type":"string","description":"Schedule CRON syntax.","default":"","x-example":null},"timeout":{"type":"integer","description":"Function maximum execution time in seconds.","default":15,"x-example":1}},"required":["name","execute"]}}]},"delete":{"summary":"Delete Function","operationId":"functionsDelete","consumes":["application\/json"],"produces":[],"tags":["functions"],"description":"Delete a function by its unique ID.","responses":{"204":{"description":"No content"}},"x-appwrite":{"method":"delete","weight":167,"cookies":false,"type":"","demo":"functions\/delete.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/functions\/delete-function.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"functions.write","platforms":["server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"functionId","description":"Function unique ID.","required":true,"type":"string","x-example":"[FUNCTION_ID]","in":"path"}]}},"\/functions\/{functionId}\/executions":{"get":{"summary":"List Executions","operationId":"functionsListExecutions","consumes":["application\/json"],"produces":["application\/json"],"tags":["functions"],"description":"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 executions. [Learn more about different API modes](\/docs\/admin).","responses":{"200":{"description":"Executions List","schema":{"$ref":"#\/definitions\/executionList"}}},"x-appwrite":{"method":"listExecutions","weight":173,"cookies":false,"type":"","demo":"functions\/list-executions.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/functions\/list-executions.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"execution.read","platforms":["client","server","server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[],"JWT":[]}],"parameters":[{"name":"functionId","description":"Function unique ID.","required":true,"type":"string","x-example":"[FUNCTION_ID]","in":"path"},{"name":"limit","description":"Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.","required":false,"type":"integer","format":"int32","x-example":0,"default":25,"in":"query"},{"name":"offset","description":"Results offset. The default value is 0. Use this param to manage pagination.","required":false,"type":"integer","format":"int32","x-example":0,"default":0,"in":"query"}]},"post":{"summary":"Create Execution","operationId":"functionsCreateExecution","consumes":["application\/json"],"produces":["application\/json"],"tags":["functions"],"description":"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.","responses":{"201":{"description":"Execution","schema":{"$ref":"#\/definitions\/execution"}}},"x-appwrite":{"method":"createExecution","weight":172,"cookies":false,"type":"","demo":"functions\/create-execution.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/functions\/create-execution.md","rate-limit":60,"rate-time":60,"rate-key":"url:{url},ip:{ip}","scope":"execution.write","platforms":["client","server","server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[],"JWT":[]}],"parameters":[{"name":"functionId","description":"Function unique ID.","required":true,"type":"string","x-example":"[FUNCTION_ID]","in":"path"},{"name":"payload","in":"body","schema":{"type":"object","properties":{"data":{"type":"string","description":"String of custom data to send to function.","default":"","x-example":"[DATA]"}}}}]}},"\/functions\/{functionId}\/executions\/{executionId}":{"get":{"summary":"Get Execution","operationId":"functionsGetExecution","consumes":["application\/json"],"produces":["application\/json"],"tags":["functions"],"description":"Get a function execution log by its unique ID.","responses":{"200":{"description":"Execution","schema":{"$ref":"#\/definitions\/execution"}}},"x-appwrite":{"method":"getExecution","weight":174,"cookies":false,"type":"","demo":"functions\/get-execution.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/functions\/get-execution.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"execution.read","platforms":["client","server","server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[],"JWT":[]}],"parameters":[{"name":"functionId","description":"Function unique ID.","required":true,"type":"string","x-example":"[FUNCTION_ID]","in":"path"},{"name":"executionId","description":"Execution unique ID.","required":true,"type":"string","x-example":"[EXECUTION_ID]","in":"path"}]}},"\/functions\/{functionId}\/tag":{"patch":{"summary":"Update Function Tag","operationId":"functionsUpdateTag","consumes":["application\/json"],"produces":["application\/json"],"tags":["functions"],"description":"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.","responses":{"200":{"description":"Function","schema":{"$ref":"#\/definitions\/function"}}},"x-appwrite":{"method":"updateTag","weight":166,"cookies":false,"type":"","demo":"functions\/update-tag.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/functions\/update-function-tag.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"functions.write","platforms":["server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"functionId","description":"Function unique ID.","required":true,"type":"string","x-example":"[FUNCTION_ID]","in":"path"},{"name":"payload","in":"body","schema":{"type":"object","properties":{"tag":{"type":"string","description":"Tag unique ID.","default":null,"x-example":"[TAG]"}},"required":["tag"]}}]}},"\/functions\/{functionId}\/tags":{"get":{"summary":"List Tags","operationId":"functionsListTags","consumes":["application\/json"],"produces":["application\/json"],"tags":["functions"],"description":"Get a list of all the project's code tags. You can use the query params to filter your results.","responses":{"200":{"description":"Tags List","schema":{"$ref":"#\/definitions\/tagList"}}},"x-appwrite":{"method":"listTags","weight":169,"cookies":false,"type":"","demo":"functions\/list-tags.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/functions\/list-tags.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"functions.read","platforms":["server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"functionId","description":"Function unique ID.","required":true,"type":"string","x-example":"[FUNCTION_ID]","in":"path"},{"name":"search","description":"Search term to filter your list results. Max length: 256 chars.","required":false,"type":"string","x-example":"[SEARCH]","default":"","in":"query"},{"name":"limit","description":"Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.","required":false,"type":"integer","format":"int32","x-example":0,"default":25,"in":"query"},{"name":"offset","description":"Results offset. The default value is 0. Use this param to manage pagination.","required":false,"type":"integer","format":"int32","x-example":0,"default":0,"in":"query"},{"name":"orderType","description":"Order result by ASC or DESC order.","required":false,"type":"string","x-example":"ASC","default":"ASC","in":"query"}]},"post":{"summary":"Create Tag","operationId":"functionsCreateTag","consumes":["multipart\/form-data"],"produces":["application\/json"],"tags":["functions"],"description":"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.\n\nThis 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).\n\nUse the \"command\" param to set the entry point used to execute your code.","responses":{"201":{"description":"Tag","schema":{"$ref":"#\/definitions\/tag"}}},"x-appwrite":{"method":"createTag","weight":168,"cookies":false,"type":"","demo":"functions\/create-tag.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/functions\/create-tag.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"functions.write","platforms":["server"],"packaging":true,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"functionId","description":"Function unique ID.","required":true,"type":"string","x-example":"[FUNCTION_ID]","in":"path"},{"name":"command","description":"Code execution command.","required":true,"type":"string","x-example":"[COMMAND]","in":"formData"},{"name":"code","description":"Gzip file with your code package. When used with the Appwrite CLI, pass the path to your code directory, and the CLI will automatically package your code. Use a path that is within the current directory.","required":true,"type":"file","in":"formData"}]}},"\/functions\/{functionId}\/tags\/{tagId}":{"get":{"summary":"Get Tag","operationId":"functionsGetTag","consumes":["application\/json"],"produces":["application\/json"],"tags":["functions"],"description":"Get a code tag by its unique ID.","responses":{"200":{"description":"Tag","schema":{"$ref":"#\/definitions\/tag"}}},"x-appwrite":{"method":"getTag","weight":170,"cookies":false,"type":"","demo":"functions\/get-tag.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/functions\/get-tag.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"functions.read","platforms":["server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"functionId","description":"Function unique ID.","required":true,"type":"string","x-example":"[FUNCTION_ID]","in":"path"},{"name":"tagId","description":"Tag unique ID.","required":true,"type":"string","x-example":"[TAG_ID]","in":"path"}]},"delete":{"summary":"Delete Tag","operationId":"functionsDeleteTag","consumes":["application\/json"],"produces":[],"tags":["functions"],"description":"Delete a code tag by its unique ID.","responses":{"204":{"description":"No content"}},"x-appwrite":{"method":"deleteTag","weight":171,"cookies":false,"type":"","demo":"functions\/delete-tag.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/functions\/delete-tag.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"functions.write","platforms":["server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"functionId","description":"Function unique ID.","required":true,"type":"string","x-example":"[FUNCTION_ID]","in":"path"},{"name":"tagId","description":"Tag unique ID.","required":true,"type":"string","x-example":"[TAG_ID]","in":"path"}]}},"\/functions\/{functionId}\/usage":{"get":{"summary":"Get Function Usage","operationId":"functionsGetUsage","consumes":["application\/json"],"produces":[],"tags":["functions"],"description":"","responses":{"500":{"description":"File","schema":{"type":"file"}}},"x-appwrite":{"method":"getUsage","weight":164,"cookies":false,"type":"","demo":"functions\/get-usage.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"functions.read","platforms":["console"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[]}],"parameters":[{"name":"functionId","description":"Function unique ID.","required":true,"type":"string","x-example":"[FUNCTION_ID]","in":"path"},{"name":"range","description":"Date range.","required":false,"type":"string","x-example":"24h","default":"30d","in":"query"}]}},"\/health":{"get":{"summary":"Get HTTP","operationId":"healthGet","consumes":["application\/json"],"produces":[],"tags":["health"],"description":"Check the Appwrite HTTP server is up and responsive.","responses":{"500":{"description":"File","schema":{"type":"file"}}},"x-appwrite":{"method":"get","weight":83,"cookies":false,"type":"","demo":"health\/get.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"health.read","platforms":["server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[]}]}},"\/health\/anti-virus":{"get":{"summary":"Get Anti virus","operationId":"healthGetAntiVirus","consumes":["application\/json"],"produces":[],"tags":["health"],"description":"Check the Appwrite Anti Virus server is up and connection is successful.","responses":{"500":{"description":"File","schema":{"type":"file"}}},"x-appwrite":{"method":"getAntiVirus","weight":95,"cookies":false,"type":"","demo":"health\/get-anti-virus.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-storage-anti-virus.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"health.read","platforms":["server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[]}]}},"\/health\/cache":{"get":{"summary":"Get Cache","operationId":"healthGetCache","consumes":["application\/json"],"produces":[],"tags":["health"],"description":"Check the Appwrite in-memory cache server is up and connection is successful.","responses":{"500":{"description":"File","schema":{"type":"file"}}},"x-appwrite":{"method":"getCache","weight":86,"cookies":false,"type":"","demo":"health\/get-cache.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-cache.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"health.read","platforms":["server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[]}]}},"\/health\/db":{"get":{"summary":"Get DB","operationId":"healthGetDB","consumes":["application\/json"],"produces":[],"tags":["health"],"description":"Check the Appwrite database server is up and connection is successful.","responses":{"500":{"description":"File","schema":{"type":"file"}}},"x-appwrite":{"method":"getDB","weight":85,"cookies":false,"type":"","demo":"health\/get-d-b.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-db.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"health.read","platforms":["server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[]}]}},"\/health\/queue\/certificates":{"get":{"summary":"Get Certificate Queue","operationId":"healthGetQueueCertificates","consumes":["application\/json"],"produces":[],"tags":["health"],"description":"Get the number of certificates that are waiting to be issued against [Letsencrypt](https:\/\/letsencrypt.org\/) in the Appwrite internal queue server.","responses":{"500":{"description":"File","schema":{"type":"file"}}},"x-appwrite":{"method":"getQueueCertificates","weight":92,"cookies":false,"type":"","demo":"health\/get-queue-certificates.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-certificates.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"health.read","platforms":["server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[]}]}},"\/health\/queue\/functions":{"get":{"summary":"Get Functions Queue","operationId":"healthGetQueueFunctions","consumes":["application\/json"],"produces":[],"tags":["health"],"description":"","responses":{"500":{"description":"File","schema":{"type":"file"}}},"x-appwrite":{"method":"getQueueFunctions","weight":93,"cookies":false,"type":"","demo":"health\/get-queue-functions.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-functions.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"health.read","platforms":["server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[]}]}},"\/health\/queue\/logs":{"get":{"summary":"Get Logs Queue","operationId":"healthGetQueueLogs","consumes":["application\/json"],"produces":[],"tags":["health"],"description":"Get the number of logs that are waiting to be processed in the Appwrite internal queue server.","responses":{"500":{"description":"File","schema":{"type":"file"}}},"x-appwrite":{"method":"getQueueLogs","weight":90,"cookies":false,"type":"","demo":"health\/get-queue-logs.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-logs.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"health.read","platforms":["server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[]}]}},"\/health\/queue\/tasks":{"get":{"summary":"Get Tasks Queue","operationId":"healthGetQueueTasks","consumes":["application\/json"],"produces":[],"tags":["health"],"description":"Get the number of tasks that are waiting to be processed in the Appwrite internal queue server.","responses":{"500":{"description":"File","schema":{"type":"file"}}},"x-appwrite":{"method":"getQueueTasks","weight":89,"cookies":false,"type":"","demo":"health\/get-queue-tasks.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-tasks.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"health.read","platforms":["server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[]}]}},"\/health\/queue\/usage":{"get":{"summary":"Get Usage Queue","operationId":"healthGetQueueUsage","consumes":["application\/json"],"produces":[],"tags":["health"],"description":"Get the number of usage stats that are waiting to be processed in the Appwrite internal queue server.","responses":{"500":{"description":"File","schema":{"type":"file"}}},"x-appwrite":{"method":"getQueueUsage","weight":91,"cookies":false,"type":"","demo":"health\/get-queue-usage.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-usage.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"health.read","platforms":["server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[]}]}},"\/health\/queue\/webhooks":{"get":{"summary":"Get Webhooks Queue","operationId":"healthGetQueueWebhooks","consumes":["application\/json"],"produces":[],"tags":["health"],"description":"Get the number of webhooks that are waiting to be processed in the Appwrite internal queue server.","responses":{"500":{"description":"File","schema":{"type":"file"}}},"x-appwrite":{"method":"getQueueWebhooks","weight":88,"cookies":false,"type":"","demo":"health\/get-queue-webhooks.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-webhooks.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"health.read","platforms":["server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[]}]}},"\/health\/storage\/local":{"get":{"summary":"Get Local Storage","operationId":"healthGetStorageLocal","consumes":["application\/json"],"produces":[],"tags":["health"],"description":"Check the Appwrite local storage device is up and connection is successful.","responses":{"500":{"description":"File","schema":{"type":"file"}}},"x-appwrite":{"method":"getStorageLocal","weight":94,"cookies":false,"type":"","demo":"health\/get-storage-local.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-storage-local.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"health.read","platforms":["server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[]}]}},"\/health\/time":{"get":{"summary":"Get Time","operationId":"healthGetTime","consumes":["application\/json"],"produces":[],"tags":["health"],"description":"Check the Appwrite server time is synced with Google remote NTP server. We use this technology to smoothly handle leap seconds with no disruptive events. The [Network Time Protocol](https:\/\/en.wikipedia.org\/wiki\/Network_Time_Protocol) (NTP) is used by hundreds of millions of computers and devices to synchronize their clocks over the Internet. If your computer sets its own clock, it likely uses NTP.","responses":{"500":{"description":"File","schema":{"type":"file"}}},"x-appwrite":{"method":"getTime","weight":87,"cookies":false,"type":"","demo":"health\/get-time.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-time.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"health.read","platforms":["server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[]}]}},"\/locale":{"get":{"summary":"Get User Locale","operationId":"localeGet","consumes":["application\/json"],"produces":["application\/json"],"tags":["locale"],"description":"Get the current user location based on IP. Returns an object with user country code, country name, continent name, continent code, ip address and suggested currency. You can use the locale header to get the data in a supported language.\n\n([IP Geolocation by DB-IP](https:\/\/db-ip.com))","responses":{"200":{"description":"Locale","schema":{"$ref":"#\/definitions\/locale"}}},"x-appwrite":{"method":"get","weight":76,"cookies":false,"type":"","demo":"locale\/get.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/locale\/get-locale.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"locale.read","platforms":["client","server","server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[],"JWT":[]}]}},"\/locale\/continents":{"get":{"summary":"List Continents","operationId":"localeGetContinents","consumes":["application\/json"],"produces":["application\/json"],"tags":["locale"],"description":"List of all continents. You can use the locale header to get the data in a supported language.","responses":{"200":{"description":"Continents List","schema":{"$ref":"#\/definitions\/continentList"}}},"x-appwrite":{"method":"getContinents","weight":80,"cookies":false,"type":"","demo":"locale\/get-continents.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/locale\/get-continents.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"locale.read","platforms":["client","server","server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[],"JWT":[]}]}},"\/locale\/countries":{"get":{"summary":"List Countries","operationId":"localeGetCountries","consumes":["application\/json"],"produces":["application\/json"],"tags":["locale"],"description":"List of all countries. You can use the locale header to get the data in a supported language.","responses":{"200":{"description":"Countries List","schema":{"$ref":"#\/definitions\/countryList"}}},"x-appwrite":{"method":"getCountries","weight":77,"cookies":false,"type":"","demo":"locale\/get-countries.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/locale\/get-countries.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"locale.read","platforms":["client","server","server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[],"JWT":[]}]}},"\/locale\/countries\/eu":{"get":{"summary":"List EU Countries","operationId":"localeGetCountriesEU","consumes":["application\/json"],"produces":["application\/json"],"tags":["locale"],"description":"List of all countries that are currently members of the EU. You can use the locale header to get the data in a supported language.","responses":{"200":{"description":"Countries List","schema":{"$ref":"#\/definitions\/countryList"}}},"x-appwrite":{"method":"getCountriesEU","weight":78,"cookies":false,"type":"","demo":"locale\/get-countries-e-u.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/locale\/get-countries-eu.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"locale.read","platforms":["client","server","server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[],"JWT":[]}]}},"\/locale\/countries\/phones":{"get":{"summary":"List Countries Phone Codes","operationId":"localeGetCountriesPhones","consumes":["application\/json"],"produces":["application\/json"],"tags":["locale"],"description":"List of all countries phone codes. You can use the locale header to get the data in a supported language.","responses":{"200":{"description":"Phones List","schema":{"$ref":"#\/definitions\/phoneList"}}},"x-appwrite":{"method":"getCountriesPhones","weight":79,"cookies":false,"type":"","demo":"locale\/get-countries-phones.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/locale\/get-countries-phones.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"locale.read","platforms":["client","server","server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[],"JWT":[]}]}},"\/locale\/currencies":{"get":{"summary":"List Currencies","operationId":"localeGetCurrencies","consumes":["application\/json"],"produces":["application\/json"],"tags":["locale"],"description":"List of all currencies, including currency symbol, name, plural, and decimal digits for all major and minor currencies. You can use the locale header to get the data in a supported language.","responses":{"200":{"description":"Currencies List","schema":{"$ref":"#\/definitions\/currencyList"}}},"x-appwrite":{"method":"getCurrencies","weight":81,"cookies":false,"type":"","demo":"locale\/get-currencies.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/locale\/get-currencies.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"locale.read","platforms":["client","server","server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[],"JWT":[]}]}},"\/locale\/languages":{"get":{"summary":"List Languages","operationId":"localeGetLanguages","consumes":["application\/json"],"produces":["application\/json"],"tags":["locale"],"description":"List of all languages classified by ISO 639-1 including 2-letter code, name in English, and name in the respective language.","responses":{"200":{"description":"Languages List","schema":{"$ref":"#\/definitions\/languageList"}}},"x-appwrite":{"method":"getLanguages","weight":82,"cookies":false,"type":"","demo":"locale\/get-languages.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/locale\/get-languages.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"locale.read","platforms":["client","server","server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[],"JWT":[]}]}},"\/projects":{"get":{"summary":"List Projects","operationId":"projectsList","consumes":["application\/json"],"produces":["application\/json"],"tags":["projects"],"description":"","responses":{"200":{"description":"Projects List","schema":{"$ref":"#\/definitions\/projectList"}}},"x-appwrite":{"method":"list","weight":98,"cookies":false,"type":"","demo":"projects\/list.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"projects.read","platforms":["console"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[]}],"parameters":[{"name":"search","description":"Search term to filter your list results. Max length: 256 chars.","required":false,"type":"string","x-example":"[SEARCH]","default":"","in":"query"},{"name":"limit","description":"Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.","required":false,"type":"integer","format":"int32","x-example":0,"default":25,"in":"query"},{"name":"offset","description":"Results offset. The default value is 0. Use this param to manage pagination.","required":false,"type":"integer","format":"int32","x-example":0,"default":0,"in":"query"},{"name":"orderType","description":"Order result by ASC or DESC order.","required":false,"type":"string","x-example":"ASC","default":"ASC","in":"query"}]},"post":{"summary":"Create Project","operationId":"projectsCreate","consumes":["application\/json"],"produces":["application\/json"],"tags":["projects"],"description":"","responses":{"201":{"description":"Project","schema":{"$ref":"#\/definitions\/project"}}},"x-appwrite":{"method":"create","weight":97,"cookies":false,"type":"","demo":"projects\/create.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"projects.write","platforms":["console"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[]}],"parameters":[{"name":"payload","in":"body","schema":{"type":"object","properties":{"name":{"type":"string","description":"Project name. Max length: 128 chars.","default":null,"x-example":"[NAME]"},"teamId":{"type":"string","description":"Team unique ID.","default":null,"x-example":null},"description":{"type":"string","description":"Project description. Max length: 256 chars.","default":"","x-example":"[DESCRIPTION]"},"logo":{"type":"string","description":"Project logo.","default":"","x-example":"[LOGO]"},"url":{"type":"string","description":"Project URL.","default":"","x-example":"https:\/\/example.com"},"legalName":{"type":"string","description":"Project legal Name. Max length: 256 chars.","default":"","x-example":"[LEGAL_NAME]"},"legalCountry":{"type":"string","description":"Project legal Country. Max length: 256 chars.","default":"","x-example":"[LEGAL_COUNTRY]"},"legalState":{"type":"string","description":"Project legal State. Max length: 256 chars.","default":"","x-example":"[LEGAL_STATE]"},"legalCity":{"type":"string","description":"Project legal City. Max length: 256 chars.","default":"","x-example":"[LEGAL_CITY]"},"legalAddress":{"type":"string","description":"Project legal Address. Max length: 256 chars.","default":"","x-example":"[LEGAL_ADDRESS]"},"legalTaxId":{"type":"string","description":"Project legal Tax ID. Max length: 256 chars.","default":"","x-example":"[LEGAL_TAX_ID]"}},"required":["name","teamId"]}}]}},"\/projects\/{projectId}":{"get":{"summary":"Get Project","operationId":"projectsGet","consumes":["application\/json"],"produces":["application\/json"],"tags":["projects"],"description":"","responses":{"200":{"description":"Project","schema":{"$ref":"#\/definitions\/project"}}},"x-appwrite":{"method":"get","weight":99,"cookies":false,"type":"","demo":"projects\/get.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"projects.read","platforms":["console"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[]}],"parameters":[{"name":"projectId","description":"Project unique ID.","required":true,"type":"string","in":"path"}]},"patch":{"summary":"Update Project","operationId":"projectsUpdate","consumes":["application\/json"],"produces":["application\/json"],"tags":["projects"],"description":"","responses":{"200":{"description":"Project","schema":{"$ref":"#\/definitions\/project"}}},"x-appwrite":{"method":"update","weight":101,"cookies":false,"type":"","demo":"projects\/update.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"projects.write","platforms":["console"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[]}],"parameters":[{"name":"projectId","description":"Project unique ID.","required":true,"type":"string","in":"path"},{"name":"payload","in":"body","schema":{"type":"object","properties":{"name":{"type":"string","description":"Project name. Max length: 128 chars.","default":null,"x-example":"[NAME]"},"description":{"type":"string","description":"Project description. Max length: 256 chars.","default":"","x-example":"[DESCRIPTION]"},"logo":{"type":"string","description":"Project logo.","default":"","x-example":"[LOGO]"},"url":{"type":"string","description":"Project URL.","default":"","x-example":"https:\/\/example.com"},"legalName":{"type":"string","description":"Project legal name. Max length: 256 chars.","default":"","x-example":"[LEGAL_NAME]"},"legalCountry":{"type":"string","description":"Project legal country. Max length: 256 chars.","default":"","x-example":"[LEGAL_COUNTRY]"},"legalState":{"type":"string","description":"Project legal state. Max length: 256 chars.","default":"","x-example":"[LEGAL_STATE]"},"legalCity":{"type":"string","description":"Project legal city. Max length: 256 chars.","default":"","x-example":"[LEGAL_CITY]"},"legalAddress":{"type":"string","description":"Project legal address. Max length: 256 chars.","default":"","x-example":"[LEGAL_ADDRESS]"},"legalTaxId":{"type":"string","description":"Project legal tax ID. Max length: 256 chars.","default":"","x-example":"[LEGAL_TAX_ID]"}},"required":["name"]}}]},"delete":{"summary":"Delete Project","operationId":"projectsDelete","consumes":["application\/json"],"produces":[],"tags":["projects"],"description":"","responses":{"204":{"description":"No content"}},"x-appwrite":{"method":"delete","weight":105,"cookies":false,"type":"","demo":"projects\/delete.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"projects.write","platforms":["console"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[]}],"parameters":[{"name":"projectId","description":"Project unique ID.","required":true,"type":"string","in":"path"},{"name":"payload","in":"body","schema":{"type":"object","properties":{"password":{"type":"string","description":"Your user password for confirmation. Must be between 6 to 32 chars.","default":null,"x-example":null}},"required":["password"]}}]}},"\/projects\/{projectId}\/auth\/limit":{"patch":{"summary":"Update Project users limit","operationId":"projectsUpdateAuthLimit","consumes":["application\/json"],"produces":["application\/json"],"tags":["projects"],"description":"","responses":{"200":{"description":"Project","schema":{"$ref":"#\/definitions\/project"}}},"x-appwrite":{"method":"updateAuthLimit","weight":103,"cookies":false,"type":"","demo":"projects\/update-auth-limit.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"projects.write","platforms":["console"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[]}],"parameters":[{"name":"projectId","description":"Project unique ID.","required":true,"type":"string","in":"path"},{"name":"payload","in":"body","schema":{"type":"object","properties":{"limit":{"type":"integer","description":"Set the max number of users allowed in this project. Use 0 for unlimited.","default":null,"x-example":null}},"required":["limit"]}}]}},"\/projects\/{projectId}\/auth\/{method}":{"patch":{"summary":"Update Project auth method status. Use this endpoint to enable or disable a given auth method for this project.","operationId":"projectsUpdateAuthStatus","consumes":["application\/json"],"produces":["application\/json"],"tags":["projects"],"description":"","responses":{"200":{"description":"Project","schema":{"$ref":"#\/definitions\/project"}}},"x-appwrite":{"method":"updateAuthStatus","weight":104,"cookies":false,"type":"","demo":"projects\/update-auth-status.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"projects.write","platforms":["console"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[]}],"parameters":[{"name":"projectId","description":"Project unique ID.","required":true,"type":"string","in":"path"},{"name":"method","description":"Auth Method. Possible values: email-password,anonymous,invites,jwt,phone","required":true,"type":"string","x-example":"email-password","in":"path"},{"name":"payload","in":"body","schema":{"type":"object","properties":{"status":{"type":"boolean","description":"Set the status of this auth method.","default":null,"x-example":false}},"required":["status"]}}]}},"\/projects\/{projectId}\/domains":{"get":{"summary":"List Domains","operationId":"projectsListDomains","consumes":["application\/json"],"produces":["application\/json"],"tags":["projects"],"description":"","responses":{"200":{"description":"Domains List","schema":{"$ref":"#\/definitions\/domainList"}}},"x-appwrite":{"method":"listDomains","weight":127,"cookies":false,"type":"","demo":"projects\/list-domains.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"projects.read","platforms":["console"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[]}],"parameters":[{"name":"projectId","description":"Project unique ID.","required":true,"type":"string","in":"path"}]},"post":{"summary":"Create Domain","operationId":"projectsCreateDomain","consumes":["application\/json"],"produces":["application\/json"],"tags":["projects"],"description":"","responses":{"201":{"description":"Domain","schema":{"$ref":"#\/definitions\/domain"}}},"x-appwrite":{"method":"createDomain","weight":126,"cookies":false,"type":"","demo":"projects\/create-domain.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"projects.write","platforms":["console"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[]}],"parameters":[{"name":"projectId","description":"Project unique ID.","required":true,"type":"string","in":"path"},{"name":"payload","in":"body","schema":{"type":"object","properties":{"domain":{"type":"string","description":"Domain name.","default":null,"x-example":null}},"required":["domain"]}}]}},"\/projects\/{projectId}\/domains\/{domainId}":{"get":{"summary":"Get Domain","operationId":"projectsGetDomain","consumes":["application\/json"],"produces":["application\/json"],"tags":["projects"],"description":"","responses":{"200":{"description":"Domain","schema":{"$ref":"#\/definitions\/domain"}}},"x-appwrite":{"method":"getDomain","weight":128,"cookies":false,"type":"","demo":"projects\/get-domain.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"projects.read","platforms":["console"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[]}],"parameters":[{"name":"projectId","description":"Project unique ID.","required":true,"type":"string","in":"path"},{"name":"domainId","description":"Domain unique ID.","required":true,"type":"string","in":"path"}]},"delete":{"summary":"Delete Domain","operationId":"projectsDeleteDomain","consumes":["application\/json"],"produces":[],"tags":["projects"],"description":"","responses":{"204":{"description":"No content"}},"x-appwrite":{"method":"deleteDomain","weight":130,"cookies":false,"type":"","demo":"projects\/delete-domain.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"projects.write","platforms":["console"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[]}],"parameters":[{"name":"projectId","description":"Project unique ID.","required":true,"type":"string","in":"path"},{"name":"domainId","description":"Domain unique ID.","required":true,"type":"string","in":"path"}]}},"\/projects\/{projectId}\/domains\/{domainId}\/verification":{"patch":{"summary":"Update Domain Verification Status","operationId":"projectsUpdateDomainVerification","consumes":["application\/json"],"produces":["application\/json"],"tags":["projects"],"description":"","responses":{"200":{"description":"Domain","schema":{"$ref":"#\/definitions\/domain"}}},"x-appwrite":{"method":"updateDomainVerification","weight":129,"cookies":false,"type":"","demo":"projects\/update-domain-verification.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"projects.write","platforms":["console"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[]}],"parameters":[{"name":"projectId","description":"Project unique ID.","required":true,"type":"string","in":"path"},{"name":"domainId","description":"Domain unique ID.","required":true,"type":"string","in":"path"}]}},"\/projects\/{projectId}\/keys":{"get":{"summary":"List Keys","operationId":"projectsListKeys","consumes":["application\/json"],"produces":["application\/json"],"tags":["projects"],"description":"","responses":{"200":{"description":"API Keys List","schema":{"$ref":"#\/definitions\/keyList"}}},"x-appwrite":{"method":"listKeys","weight":112,"cookies":false,"type":"","demo":"projects\/list-keys.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"projects.read","platforms":["console"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[]}],"parameters":[{"name":"projectId","description":"Project unique ID.","required":true,"type":"string","in":"path"}]},"post":{"summary":"Create Key","operationId":"projectsCreateKey","consumes":["application\/json"],"produces":["application\/json"],"tags":["projects"],"description":"","responses":{"201":{"description":"Key","schema":{"$ref":"#\/definitions\/key"}}},"x-appwrite":{"method":"createKey","weight":111,"cookies":false,"type":"","demo":"projects\/create-key.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"projects.write","platforms":["console"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[]}],"parameters":[{"name":"projectId","description":"Project unique ID.","required":true,"type":"string","in":"path"},{"name":"payload","in":"body","schema":{"type":"object","properties":{"name":{"type":"string","description":"Key name. Max length: 128 chars.","default":null,"x-example":"[NAME]"},"scopes":{"type":"array","description":"Key scopes list.","default":null,"x-example":null,"items":{"type":"string"}}},"required":["name","scopes"]}}]}},"\/projects\/{projectId}\/keys\/{keyId}":{"get":{"summary":"Get Key","operationId":"projectsGetKey","consumes":["application\/json"],"produces":["application\/json"],"tags":["projects"],"description":"","responses":{"200":{"description":"Key","schema":{"$ref":"#\/definitions\/key"}}},"x-appwrite":{"method":"getKey","weight":113,"cookies":false,"type":"","demo":"projects\/get-key.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"projects.read","platforms":["console"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[]}],"parameters":[{"name":"projectId","description":"Project unique ID.","required":true,"type":"string","in":"path"},{"name":"keyId","description":"Key unique ID.","required":true,"type":"string","in":"path"}]},"put":{"summary":"Update Key","operationId":"projectsUpdateKey","consumes":["application\/json"],"produces":["application\/json"],"tags":["projects"],"description":"","responses":{"200":{"description":"Key","schema":{"$ref":"#\/definitions\/key"}}},"x-appwrite":{"method":"updateKey","weight":114,"cookies":false,"type":"","demo":"projects\/update-key.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"projects.write","platforms":["console"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[]}],"parameters":[{"name":"projectId","description":"Project unique ID.","required":true,"type":"string","in":"path"},{"name":"keyId","description":"Key unique ID.","required":true,"type":"string","in":"path"},{"name":"payload","in":"body","schema":{"type":"object","properties":{"name":{"type":"string","description":"Key name. Max length: 128 chars.","default":null,"x-example":"[NAME]"},"scopes":{"type":"array","description":"Key scopes list","default":null,"x-example":null,"items":{"type":"string"}}},"required":["name","scopes"]}}]},"delete":{"summary":"Delete Key","operationId":"projectsDeleteKey","consumes":["application\/json"],"produces":[],"tags":["projects"],"description":"","responses":{"204":{"description":"No content"}},"x-appwrite":{"method":"deleteKey","weight":115,"cookies":false,"type":"","demo":"projects\/delete-key.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"projects.write","platforms":["console"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[]}],"parameters":[{"name":"projectId","description":"Project unique ID.","required":true,"type":"string","in":"path"},{"name":"keyId","description":"Key unique ID.","required":true,"type":"string","in":"path"}]}},"\/projects\/{projectId}\/oauth2":{"patch":{"summary":"Update Project OAuth2","operationId":"projectsUpdateOAuth2","consumes":["application\/json"],"produces":["application\/json"],"tags":["projects"],"description":"","responses":{"200":{"description":"Project","schema":{"$ref":"#\/definitions\/project"}}},"x-appwrite":{"method":"updateOAuth2","weight":102,"cookies":false,"type":"","demo":"projects\/update-o-auth2.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"projects.write","platforms":["console"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[]}],"parameters":[{"name":"projectId","description":"Project unique ID.","required":true,"type":"string","in":"path"},{"name":"payload","in":"body","schema":{"type":"object","properties":{"provider":{"type":"string","description":"Provider Name","default":null,"x-example":"amazon"},"appId":{"type":"string","description":"Provider app ID. Max length: 256 chars.","default":"","x-example":"[APP_ID]"},"secret":{"type":"string","description":"Provider secret key. Max length: 512 chars.","default":"","x-example":"[SECRET]"}},"required":["provider"]}}]}},"\/projects\/{projectId}\/platforms":{"get":{"summary":"List Platforms","operationId":"projectsListPlatforms","consumes":["application\/json"],"produces":["application\/json"],"tags":["projects"],"description":"","responses":{"200":{"description":"Platforms List","schema":{"$ref":"#\/definitions\/platformList"}}},"x-appwrite":{"method":"listPlatforms","weight":122,"cookies":false,"type":"","demo":"projects\/list-platforms.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"projects.read","platforms":["console"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[]}],"parameters":[{"name":"projectId","description":"Project unique ID.","required":true,"type":"string","in":"path"}]},"post":{"summary":"Create Platform","operationId":"projectsCreatePlatform","consumes":["application\/json"],"produces":["application\/json"],"tags":["projects"],"description":"","responses":{"201":{"description":"Platform","schema":{"$ref":"#\/definitions\/platform"}}},"x-appwrite":{"method":"createPlatform","weight":121,"cookies":false,"type":"","demo":"projects\/create-platform.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"projects.write","platforms":["console"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[]}],"parameters":[{"name":"projectId","description":"Project unique ID.","required":true,"type":"string","in":"path"},{"name":"payload","in":"body","schema":{"type":"object","properties":{"type":{"type":"string","description":"Platform type.","default":null,"x-example":"web"},"name":{"type":"string","description":"Platform name. Max length: 128 chars.","default":null,"x-example":"[NAME]"},"key":{"type":"string","description":"Package name for android or bundle ID for iOS. Max length: 256 chars.","default":"","x-example":"[KEY]"},"store":{"type":"string","description":"App store or Google Play store ID. Max length: 256 chars.","default":"","x-example":"[STORE]"},"hostname":{"type":"string","description":"Platform client hostname. Max length: 256 chars.","default":"","x-example":"[HOSTNAME]"}},"required":["type","name"]}}]}},"\/projects\/{projectId}\/platforms\/{platformId}":{"get":{"summary":"Get Platform","operationId":"projectsGetPlatform","consumes":["application\/json"],"produces":["application\/json"],"tags":["projects"],"description":"","responses":{"200":{"description":"Platform","schema":{"$ref":"#\/definitions\/platform"}}},"x-appwrite":{"method":"getPlatform","weight":123,"cookies":false,"type":"","demo":"projects\/get-platform.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"projects.read","platforms":["console"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[]}],"parameters":[{"name":"projectId","description":"Project unique ID.","required":true,"type":"string","in":"path"},{"name":"platformId","description":"Platform unique ID.","required":true,"type":"string","in":"path"}]},"put":{"summary":"Update Platform","operationId":"projectsUpdatePlatform","consumes":["application\/json"],"produces":["application\/json"],"tags":["projects"],"description":"","responses":{"200":{"description":"Platform","schema":{"$ref":"#\/definitions\/platform"}}},"x-appwrite":{"method":"updatePlatform","weight":124,"cookies":false,"type":"","demo":"projects\/update-platform.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"projects.write","platforms":["console"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[]}],"parameters":[{"name":"projectId","description":"Project unique ID.","required":true,"type":"string","in":"path"},{"name":"platformId","description":"Platform unique ID.","required":true,"type":"string","in":"path"},{"name":"payload","in":"body","schema":{"type":"object","properties":{"name":{"type":"string","description":"Platform name. Max length: 128 chars.","default":null,"x-example":"[NAME]"},"key":{"type":"string","description":"Package name for android or bundle ID for iOS. Max length: 256 chars.","default":"","x-example":"[KEY]"},"store":{"type":"string","description":"App store or Google Play store ID. Max length: 256 chars.","default":"","x-example":"[STORE]"},"hostname":{"type":"string","description":"Platform client URL. Max length: 256 chars.","default":"","x-example":"[HOSTNAME]"}},"required":["name"]}}]},"delete":{"summary":"Delete Platform","operationId":"projectsDeletePlatform","consumes":["application\/json"],"produces":[],"tags":["projects"],"description":"","responses":{"204":{"description":"No content"}},"x-appwrite":{"method":"deletePlatform","weight":125,"cookies":false,"type":"","demo":"projects\/delete-platform.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"projects.write","platforms":["console"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[]}],"parameters":[{"name":"projectId","description":"Project unique ID.","required":true,"type":"string","in":"path"},{"name":"platformId","description":"Platform unique ID.","required":true,"type":"string","in":"path"}]}},"\/projects\/{projectId}\/tasks":{"get":{"summary":"List Tasks","operationId":"projectsListTasks","consumes":["application\/json"],"produces":["application\/json"],"tags":["projects"],"description":"","responses":{"200":{"description":"Tasks List","schema":{"$ref":"#\/definitions\/taskList"}}},"x-appwrite":{"method":"listTasks","weight":117,"cookies":false,"type":"","demo":"projects\/list-tasks.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"projects.read","platforms":["console"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[]}],"parameters":[{"name":"projectId","description":"Project unique ID.","required":true,"type":"string","in":"path"}]},"post":{"summary":"Create Task","operationId":"projectsCreateTask","consumes":["application\/json"],"produces":["application\/json"],"tags":["projects"],"description":"","responses":{"201":{"description":"Task","schema":{"$ref":"#\/definitions\/task"}}},"x-appwrite":{"method":"createTask","weight":116,"cookies":false,"type":"","demo":"projects\/create-task.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"projects.write","platforms":["console"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[]}],"parameters":[{"name":"projectId","description":"Project unique ID.","required":true,"type":"string","in":"path"},{"name":"payload","in":"body","schema":{"type":"object","properties":{"name":{"type":"string","description":"Task name. Max length: 128 chars.","default":null,"x-example":"[NAME]"},"status":{"type":"string","description":"Task status.","default":null,"x-example":"play"},"schedule":{"type":"string","description":"Task schedule CRON syntax.","default":null,"x-example":null},"security":{"type":"boolean","description":"Certificate verification, false for disabled or true for enabled.","default":null,"x-example":false},"httpMethod":{"type":"string","description":"Task HTTP method.","default":null,"x-example":"GET"},"httpUrl":{"type":"string","description":"Task HTTP URL","default":null,"x-example":"https:\/\/example.com"},"httpHeaders":{"type":"array","description":"Task HTTP headers list.","default":null,"x-example":null,"items":{"type":"string"}},"httpUser":{"type":"string","description":"Task HTTP user. Max length: 256 chars.","default":"","x-example":"[HTTP_USER]"},"httpPass":{"type":"string","description":"Task HTTP password. Max length: 256 chars.","default":"","x-example":"[HTTP_PASS]"}},"required":["name","status","schedule","security","httpMethod","httpUrl"]}}]}},"\/projects\/{projectId}\/tasks\/{taskId}":{"get":{"summary":"Get Task","operationId":"projectsGetTask","consumes":["application\/json"],"produces":["application\/json"],"tags":["projects"],"description":"","responses":{"200":{"description":"Task","schema":{"$ref":"#\/definitions\/task"}}},"x-appwrite":{"method":"getTask","weight":118,"cookies":false,"type":"","demo":"projects\/get-task.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"projects.read","platforms":["console"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[]}],"parameters":[{"name":"projectId","description":"Project unique ID.","required":true,"type":"string","in":"path"},{"name":"taskId","description":"Task unique ID.","required":true,"type":"string","in":"path"}]},"put":{"summary":"Update Task","operationId":"projectsUpdateTask","consumes":["application\/json"],"produces":["application\/json"],"tags":["projects"],"description":"","responses":{"200":{"description":"Task","schema":{"$ref":"#\/definitions\/task"}}},"x-appwrite":{"method":"updateTask","weight":119,"cookies":false,"type":"","demo":"projects\/update-task.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"projects.write","platforms":["console"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[]}],"parameters":[{"name":"projectId","description":"Project unique ID.","required":true,"type":"string","in":"path"},{"name":"taskId","description":"Task unique ID.","required":true,"type":"string","in":"path"},{"name":"payload","in":"body","schema":{"type":"object","properties":{"name":{"type":"string","description":"Task name. Max length: 128 chars.","default":null,"x-example":"[NAME]"},"status":{"type":"string","description":"Task status.","default":null,"x-example":"play"},"schedule":{"type":"string","description":"Task schedule CRON syntax.","default":null,"x-example":null},"security":{"type":"boolean","description":"Certificate verification, false for disabled or true for enabled.","default":null,"x-example":false},"httpMethod":{"type":"string","description":"Task HTTP method.","default":null,"x-example":"GET"},"httpUrl":{"type":"string","description":"Task HTTP URL.","default":null,"x-example":"https:\/\/example.com"},"httpHeaders":{"type":"array","description":"Task HTTP headers list.","default":null,"x-example":null,"items":{"type":"string"}},"httpUser":{"type":"string","description":"Task HTTP user. Max length: 256 chars.","default":"","x-example":"[HTTP_USER]"},"httpPass":{"type":"string","description":"Task HTTP password. Max length: 256 chars.","default":"","x-example":"[HTTP_PASS]"}},"required":["name","status","schedule","security","httpMethod","httpUrl"]}}]},"delete":{"summary":"Delete Task","operationId":"projectsDeleteTask","consumes":["application\/json"],"produces":[],"tags":["projects"],"description":"","responses":{"204":{"description":"No content"}},"x-appwrite":{"method":"deleteTask","weight":120,"cookies":false,"type":"","demo":"projects\/delete-task.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"projects.write","platforms":["console"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[]}],"parameters":[{"name":"projectId","description":"Project unique ID.","required":true,"type":"string","in":"path"},{"name":"taskId","description":"Task unique ID.","required":true,"type":"string","in":"path"}]}},"\/projects\/{projectId}\/usage":{"get":{"summary":"Get Project","operationId":"projectsGetUsage","consumes":["application\/json"],"produces":[],"tags":["projects"],"description":"","responses":{"500":{"description":"File","schema":{"type":"file"}}},"x-appwrite":{"method":"getUsage","weight":100,"cookies":false,"type":"","demo":"projects\/get-usage.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"projects.read","platforms":["console"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[]}],"parameters":[{"name":"projectId","description":"Project unique ID.","required":true,"type":"string","in":"path"},{"name":"range","description":"Date range.","required":false,"type":"string","x-example":"24h","default":"30d","in":"query"}]}},"\/projects\/{projectId}\/webhooks":{"get":{"summary":"List Webhooks","operationId":"projectsListWebhooks","consumes":["application\/json"],"produces":["application\/json"],"tags":["projects"],"description":"","responses":{"200":{"description":"Webhooks List","schema":{"$ref":"#\/definitions\/webhookList"}}},"x-appwrite":{"method":"listWebhooks","weight":107,"cookies":false,"type":"","demo":"projects\/list-webhooks.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"projects.read","platforms":["console"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[]}],"parameters":[{"name":"projectId","description":"Project unique ID.","required":true,"type":"string","in":"path"}]},"post":{"summary":"Create Webhook","operationId":"projectsCreateWebhook","consumes":["application\/json"],"produces":["application\/json"],"tags":["projects"],"description":"","responses":{"201":{"description":"Webhook","schema":{"$ref":"#\/definitions\/webhook"}}},"x-appwrite":{"method":"createWebhook","weight":106,"cookies":false,"type":"","demo":"projects\/create-webhook.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"projects.write","platforms":["console"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[]}],"parameters":[{"name":"projectId","description":"Project unique ID.","required":true,"type":"string","in":"path"},{"name":"payload","in":"body","schema":{"type":"object","properties":{"name":{"type":"string","description":"Webhook name. Max length: 128 chars.","default":null,"x-example":"[NAME]"},"events":{"type":"array","description":"Events list.","default":null,"x-example":null,"items":{"type":"string"}},"url":{"type":"string","description":"Webhook URL.","default":null,"x-example":"https:\/\/example.com"},"security":{"type":"boolean","description":"Certificate verification, false for disabled or true for enabled.","default":null,"x-example":false},"httpUser":{"type":"string","description":"Webhook HTTP user. Max length: 256 chars.","default":"","x-example":"[HTTP_USER]"},"httpPass":{"type":"string","description":"Webhook HTTP password. Max length: 256 chars.","default":"","x-example":"[HTTP_PASS]"}},"required":["name","events","url","security"]}}]}},"\/projects\/{projectId}\/webhooks\/{webhookId}":{"get":{"summary":"Get Webhook","operationId":"projectsGetWebhook","consumes":["application\/json"],"produces":["application\/json"],"tags":["projects"],"description":"","responses":{"200":{"description":"Webhook","schema":{"$ref":"#\/definitions\/webhook"}}},"x-appwrite":{"method":"getWebhook","weight":108,"cookies":false,"type":"","demo":"projects\/get-webhook.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"projects.read","platforms":["console"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[]}],"parameters":[{"name":"projectId","description":"Project unique ID.","required":true,"type":"string","in":"path"},{"name":"webhookId","description":"Webhook unique ID.","required":true,"type":"string","in":"path"}]},"put":{"summary":"Update Webhook","operationId":"projectsUpdateWebhook","consumes":["application\/json"],"produces":["application\/json"],"tags":["projects"],"description":"","responses":{"200":{"description":"Webhook","schema":{"$ref":"#\/definitions\/webhook"}}},"x-appwrite":{"method":"updateWebhook","weight":109,"cookies":false,"type":"","demo":"projects\/update-webhook.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"projects.write","platforms":["console"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[]}],"parameters":[{"name":"projectId","description":"Project unique ID.","required":true,"type":"string","in":"path"},{"name":"webhookId","description":"Webhook unique ID.","required":true,"type":"string","in":"path"},{"name":"payload","in":"body","schema":{"type":"object","properties":{"name":{"type":"string","description":"Webhook name. Max length: 128 chars.","default":null,"x-example":"[NAME]"},"events":{"type":"array","description":"Events list.","default":null,"x-example":null,"items":{"type":"string"}},"url":{"type":"string","description":"Webhook URL.","default":null,"x-example":"https:\/\/example.com"},"security":{"type":"boolean","description":"Certificate verification, false for disabled or true for enabled.","default":null,"x-example":false},"httpUser":{"type":"string","description":"Webhook HTTP user. Max length: 256 chars.","default":"","x-example":"[HTTP_USER]"},"httpPass":{"type":"string","description":"Webhook HTTP password. Max length: 256 chars.","default":"","x-example":"[HTTP_PASS]"}},"required":["name","events","url","security"]}}]},"delete":{"summary":"Delete Webhook","operationId":"projectsDeleteWebhook","consumes":["application\/json"],"produces":[],"tags":["projects"],"description":"","responses":{"204":{"description":"No content"}},"x-appwrite":{"method":"deleteWebhook","weight":110,"cookies":false,"type":"","demo":"projects\/delete-webhook.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"projects.write","platforms":["console"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[]}],"parameters":[{"name":"projectId","description":"Project unique ID.","required":true,"type":"string","in":"path"},{"name":"webhookId","description":"Webhook unique ID.","required":true,"type":"string","in":"path"}]}},"\/storage\/files":{"get":{"summary":"List Files","operationId":"storageListFiles","consumes":["application\/json"],"produces":["application\/json"],"tags":["storage"],"description":"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).","responses":{"200":{"description":"Files List","schema":{"$ref":"#\/definitions\/fileList"}}},"x-appwrite":{"method":"listFiles","weight":132,"cookies":false,"type":"","demo":"storage\/list-files.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/storage\/list-files.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"files.read","platforms":["client","server","server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[],"JWT":[]}],"parameters":[{"name":"search","description":"Search term to filter your list results. Max length: 256 chars.","required":false,"type":"string","x-example":"[SEARCH]","default":"","in":"query"},{"name":"limit","description":"Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.","required":false,"type":"integer","format":"int32","x-example":0,"default":25,"in":"query"},{"name":"offset","description":"Results offset. The default value is 0. Use this param to manage pagination.","required":false,"type":"integer","format":"int32","x-example":0,"default":0,"in":"query"},{"name":"orderType","description":"Order result by ASC or DESC order.","required":false,"type":"string","x-example":"ASC","default":"ASC","in":"query"}]},"post":{"summary":"Create File","operationId":"storageCreateFile","consumes":["multipart\/form-data"],"produces":["application\/json"],"tags":["storage"],"description":"Create a new file. The user who creates the file will automatically be assigned to read and write access unless he has passed custom values for read and write arguments.","responses":{"201":{"description":"File","schema":{"$ref":"#\/definitions\/file"}}},"x-appwrite":{"method":"createFile","weight":131,"cookies":false,"type":"upload","demo":"storage\/create-file.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/storage\/create-file.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"files.write","platforms":["client","server","server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[],"JWT":[]}],"parameters":[{"name":"file","description":"Binary file.","required":true,"type":"file","in":"formData"},{"name":"read","description":"An array of strings with read permissions. By default only the current user is granted with read permissions. [learn more about permissions](\/docs\/permissions) and get a full list of available permissions.","required":false,"type":"array","collectionFormat":"multi","items":{"type":"string"},"in":"formData"},{"name":"write","description":"An array of strings with write permissions. By default only the current user is granted with write permissions. [learn more about permissions](\/docs\/permissions) and get a full list of available permissions.","required":false,"type":"array","collectionFormat":"multi","items":{"type":"string"},"in":"formData"}]}},"\/storage\/files\/{fileId}":{"get":{"summary":"Get File","operationId":"storageGetFile","consumes":["application\/json"],"produces":["application\/json"],"tags":["storage"],"description":"Get a file by its unique ID. This endpoint response returns a JSON object with the file metadata.","responses":{"200":{"description":"File","schema":{"$ref":"#\/definitions\/file"}}},"x-appwrite":{"method":"getFile","weight":133,"cookies":false,"type":"","demo":"storage\/get-file.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/storage\/get-file.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"files.read","platforms":["client","server","server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[],"JWT":[]}],"parameters":[{"name":"fileId","description":"File unique ID.","required":true,"type":"string","x-example":"[FILE_ID]","in":"path"}]},"put":{"summary":"Update File","operationId":"storageUpdateFile","consumes":["application\/json"],"produces":["application\/json"],"tags":["storage"],"description":"Update a file by its unique ID. Only users with write permissions have access to update this resource.","responses":{"200":{"description":"File","schema":{"$ref":"#\/definitions\/file"}}},"x-appwrite":{"method":"updateFile","weight":137,"cookies":false,"type":"","demo":"storage\/update-file.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/storage\/update-file.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"files.write","platforms":["client","server","server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[],"JWT":[]}],"parameters":[{"name":"fileId","description":"File unique ID.","required":true,"type":"string","x-example":"[FILE_ID]","in":"path"},{"name":"payload","in":"body","schema":{"type":"object","properties":{"read":{"type":"array","description":"An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions](\/docs\/permissions) and get a full list of available permissions.","default":null,"x-example":null,"items":{"type":"string"}},"write":{"type":"array","description":"An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](\/docs\/permissions) and get a full list of available permissions.","default":null,"x-example":null,"items":{"type":"string"}}},"required":["read","write"]}}]},"delete":{"summary":"Delete File","operationId":"storageDeleteFile","consumes":["application\/json"],"produces":[],"tags":["storage"],"description":"Delete a file by its unique ID. Only users with write permissions have access to delete this resource.","responses":{"204":{"description":"No content"}},"x-appwrite":{"method":"deleteFile","weight":138,"cookies":false,"type":"","demo":"storage\/delete-file.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/storage\/delete-file.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"files.write","platforms":["client","server","server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[],"JWT":[]}],"parameters":[{"name":"fileId","description":"File unique ID.","required":true,"type":"string","x-example":"[FILE_ID]","in":"path"}]}},"\/storage\/files\/{fileId}\/download":{"get":{"summary":"Get File for Download","operationId":"storageGetFileDownload","consumes":["application\/json"],"produces":["*\/*"],"tags":["storage"],"description":"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.","responses":{"200":{"description":"File","schema":{"type":"file"}}},"x-appwrite":{"method":"getFileDownload","weight":135,"cookies":false,"type":"location","demo":"storage\/get-file-download.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/storage\/get-file-download.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"files.read","platforms":["client","server","server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[],"JWT":[]}],"parameters":[{"name":"fileId","description":"File unique ID.","required":true,"type":"string","x-example":"[FILE_ID]","in":"path"}]}},"\/storage\/files\/{fileId}\/preview":{"get":{"summary":"Get File Preview","operationId":"storageGetFilePreview","consumes":["application\/json"],"produces":["image\/*"],"tags":["storage"],"description":"Get a file preview image. Currently, this method supports preview for image files (jpg, png, and gif), other supported formats, like pdf, docs, slides, and spreadsheets, will return the file icon image. You can also pass query string arguments for cutting and resizing your preview image.","responses":{"200":{"description":"Image","schema":{"type":"file"}}},"x-appwrite":{"method":"getFilePreview","weight":134,"cookies":false,"type":"location","demo":"storage\/get-file-preview.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/storage\/get-file-preview.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"files.read","platforms":["client","server","server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[],"JWT":[]}],"parameters":[{"name":"fileId","description":"File unique ID","required":true,"type":"string","x-example":"[FILE_ID]","in":"path"},{"name":"width","description":"Resize preview image width, Pass an integer between 0 to 4000.","required":false,"type":"integer","format":"int32","x-example":0,"default":0,"in":"query"},{"name":"height","description":"Resize preview image height, Pass an integer between 0 to 4000.","required":false,"type":"integer","format":"int32","x-example":0,"default":0,"in":"query"},{"name":"gravity","description":"Image crop gravity","required":false,"type":"string","x-example":"center","default":"center","in":"query"},{"name":"quality","description":"Preview image quality. Pass an integer between 0 to 100. Defaults to 100.","required":false,"type":"integer","format":"int32","x-example":0,"default":100,"in":"query"},{"name":"borderWidth","description":"Preview image border in pixels. Pass an integer between 0 to 100. Defaults to 0.","required":false,"type":"integer","format":"int32","x-example":0,"default":0,"in":"query"},{"name":"borderColor","description":"Preview image border color. Use a valid HEX color, no # is needed for prefix.","required":false,"type":"string","default":"","in":"query"},{"name":"borderRadius","description":"Preview image border radius in pixels. Pass an integer between 0 to 4000.","required":false,"type":"integer","format":"int32","x-example":0,"default":0,"in":"query"},{"name":"opacity","description":"Preview image opacity. Only works with images having an alpha channel (like png). Pass a number between 0 to 1.","required":false,"type":"number","format":"float","x-example":0,"default":1,"in":"query"},{"name":"rotation","description":"Preview image rotation in degrees. Pass an integer between 0 and 360.","required":false,"type":"integer","format":"int32","x-example":0,"default":0,"in":"query"},{"name":"background","description":"Preview image background color. Only works with transparent images (png). Use a valid HEX color, no # is needed for prefix.","required":false,"type":"string","default":"","in":"query"},{"name":"output","description":"Output format type (jpeg, jpg, png, gif and webp).","required":false,"type":"string","x-example":"jpg","default":"","in":"query"}]}},"\/storage\/files\/{fileId}\/view":{"get":{"summary":"Get File for View","operationId":"storageGetFileView","consumes":["application\/json"],"produces":["*\/*"],"tags":["storage"],"description":"Get a file content by its unique ID. This endpoint is similar to the download method but returns with no 'Content-Disposition: attachment' header.","responses":{"200":{"description":"File","schema":{"type":"file"}}},"x-appwrite":{"method":"getFileView","weight":136,"cookies":false,"type":"location","demo":"storage\/get-file-view.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/storage\/get-file-view.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"files.read","platforms":["client","server","server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[],"JWT":[]}],"parameters":[{"name":"fileId","description":"File unique ID.","required":true,"type":"string","x-example":"[FILE_ID]","in":"path"}]}},"\/teams":{"get":{"summary":"List Teams","operationId":"teamsList","consumes":["application\/json"],"produces":["application\/json"],"tags":["teams"],"description":"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).","responses":{"200":{"description":"Teams List","schema":{"$ref":"#\/definitions\/teamList"}}},"x-appwrite":{"method":"list","weight":140,"cookies":false,"type":"","demo":"teams\/list.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/teams\/list-teams.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"teams.read","platforms":["client","server","server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[],"JWT":[]}],"parameters":[{"name":"search","description":"Search term to filter your list results. Max length: 256 chars.","required":false,"type":"string","x-example":"[SEARCH]","default":"","in":"query"},{"name":"limit","description":"Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.","required":false,"type":"integer","format":"int32","x-example":0,"default":25,"in":"query"},{"name":"offset","description":"Results offset. The default value is 0. Use this param to manage pagination.","required":false,"type":"integer","format":"int32","x-example":0,"default":0,"in":"query"},{"name":"orderType","description":"Order result by ASC or DESC order.","required":false,"type":"string","x-example":"ASC","default":"ASC","in":"query"}]},"post":{"summary":"Create Team","operationId":"teamsCreate","consumes":["application\/json"],"produces":["application\/json"],"tags":["teams"],"description":"Create a new team. The user who creates the team will automatically be assigned as the owner of the team. The team owner can invite new members, who will be able add new owners and update or delete the team from your project.","responses":{"201":{"description":"Team","schema":{"$ref":"#\/definitions\/team"}}},"x-appwrite":{"method":"create","weight":139,"cookies":false,"type":"","demo":"teams\/create.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/teams\/create-team.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"teams.write","platforms":["client","server","server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[],"JWT":[]}],"parameters":[{"name":"payload","in":"body","schema":{"type":"object","properties":{"name":{"type":"string","description":"Team name. Max length: 128 chars.","default":null,"x-example":"[NAME]"},"roles":{"type":"array","description":"Array of strings. Use this param to set the roles in the team for the user who created it. The default role is **owner**. A role can be any string. Learn more about [roles and permissions](\/docs\/permissions). Max length for each role is 32 chars.","default":["owner"],"x-example":null,"items":{"type":"string"}}},"required":["name"]}}]}},"\/teams\/{teamId}":{"get":{"summary":"Get Team","operationId":"teamsGet","consumes":["application\/json"],"produces":["application\/json"],"tags":["teams"],"description":"Get a team by its unique ID. All team members have read access for this resource.","responses":{"200":{"description":"Team","schema":{"$ref":"#\/definitions\/team"}}},"x-appwrite":{"method":"get","weight":141,"cookies":false,"type":"","demo":"teams\/get.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/teams\/get-team.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"teams.read","platforms":["client","server","server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[],"JWT":[]}],"parameters":[{"name":"teamId","description":"Team unique ID.","required":true,"type":"string","x-example":"[TEAM_ID]","in":"path"}]},"put":{"summary":"Update Team","operationId":"teamsUpdate","consumes":["application\/json"],"produces":["application\/json"],"tags":["teams"],"description":"Update a team by its unique ID. Only team owners have write access for this resource.","responses":{"200":{"description":"Team","schema":{"$ref":"#\/definitions\/team"}}},"x-appwrite":{"method":"update","weight":142,"cookies":false,"type":"","demo":"teams\/update.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/teams\/update-team.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"teams.write","platforms":["client","server","server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[],"JWT":[]}],"parameters":[{"name":"teamId","description":"Team unique ID.","required":true,"type":"string","x-example":"[TEAM_ID]","in":"path"},{"name":"payload","in":"body","schema":{"type":"object","properties":{"name":{"type":"string","description":"Team name. Max length: 128 chars.","default":null,"x-example":"[NAME]"}},"required":["name"]}}]},"delete":{"summary":"Delete Team","operationId":"teamsDelete","consumes":["application\/json"],"produces":[],"tags":["teams"],"description":"Delete a team by its unique ID. Only team owners have write access for this resource.","responses":{"204":{"description":"No content"}},"x-appwrite":{"method":"delete","weight":143,"cookies":false,"type":"","demo":"teams\/delete.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/teams\/delete-team.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"teams.write","platforms":["client","server","server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[],"JWT":[]}],"parameters":[{"name":"teamId","description":"Team unique ID.","required":true,"type":"string","x-example":"[TEAM_ID]","in":"path"}]}},"\/teams\/{teamId}\/memberships":{"get":{"summary":"Get Team Memberships","operationId":"teamsGetMemberships","consumes":["application\/json"],"produces":["application\/json"],"tags":["teams"],"description":"Get a team members by the team unique ID. All team members have read access for this list of resources.","responses":{"200":{"description":"Memberships List","schema":{"$ref":"#\/definitions\/membershipList"}}},"x-appwrite":{"method":"getMemberships","weight":145,"cookies":false,"type":"","demo":"teams\/get-memberships.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/teams\/get-team-members.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"teams.read","platforms":["client","server","server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[],"JWT":[]}],"parameters":[{"name":"teamId","description":"Team unique ID.","required":true,"type":"string","x-example":"[TEAM_ID]","in":"path"},{"name":"search","description":"Search term to filter your list results. Max length: 256 chars.","required":false,"type":"string","x-example":"[SEARCH]","default":"","in":"query"},{"name":"limit","description":"Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.","required":false,"type":"integer","format":"int32","x-example":0,"default":25,"in":"query"},{"name":"offset","description":"Results offset. The default value is 0. Use this param to manage pagination.","required":false,"type":"integer","format":"int32","x-example":0,"default":0,"in":"query"},{"name":"orderType","description":"Order result by ASC or DESC order.","required":false,"type":"string","x-example":"ASC","default":"ASC","in":"query"}]},"post":{"summary":"Create Team Membership","operationId":"teamsCreateMembership","consumes":["application\/json"],"produces":["application\/json"],"tags":["teams"],"description":"Use this endpoint to invite a new member to join your team. An email with a link to join the team will be sent to the new member email address if the member doesn't exist in the project it will be created automatically.\n\nUse the 'URL' parameter to redirect the user from the invitation email back to your app. When the user is redirected, use the [Update Team Membership Status](\/docs\/client\/teams#teamsUpdateMembershipStatus) endpoint to allow the user to accept the invitation to the team.\n\nPlease note that in order to avoid a [Redirect Attacks](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) the only valid redirect URL's are the once from domains you have set when added your platforms in the console interface.","responses":{"201":{"description":"Membership","schema":{"$ref":"#\/definitions\/membership"}}},"x-appwrite":{"method":"createMembership","weight":144,"cookies":false,"type":"","demo":"teams\/create-membership.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/teams\/create-team-membership.md","rate-limit":10,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"teams.write","platforms":["client","server","server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[],"JWT":[]}],"parameters":[{"name":"teamId","description":"Team unique ID.","required":true,"type":"string","x-example":"[TEAM_ID]","in":"path"},{"name":"payload","in":"body","schema":{"type":"object","properties":{"email":{"type":"string","description":"New team member email.","default":null,"x-example":"email@example.com"},"name":{"type":"string","description":"New team member name. Max length: 128 chars.","default":"","x-example":"[NAME]"},"roles":{"type":"array","description":"Array of strings. Use this param to set the user roles in the team. A role can be any string. Learn more about [roles and permissions](\/docs\/permissions). Max length for each role is 32 chars.","default":null,"x-example":null,"items":{"type":"string"}},"url":{"type":"string","description":"URL to redirect the user back to your app from the invitation email. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https:\/\/cheatsheetseries.owasp.org\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.","default":null,"x-example":"https:\/\/example.com"}},"required":["email","roles","url"]}}]}},"\/teams\/{teamId}\/memberships\/{membershipId}":{"patch":{"summary":"Update Membership Roles","operationId":"teamsUpdateMembershipRoles","consumes":["application\/json"],"produces":["application\/json"],"tags":["teams"],"description":"","responses":{"200":{"description":"Membership","schema":{"$ref":"#\/definitions\/membership"}}},"x-appwrite":{"method":"updateMembershipRoles","weight":146,"cookies":false,"type":"","demo":"teams\/update-membership-roles.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/teams\/update-team-membership-roles.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"teams.write","platforms":["client","server","server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[],"JWT":[]}],"parameters":[{"name":"teamId","description":"Team unique ID.","required":true,"type":"string","x-example":"[TEAM_ID]","in":"path"},{"name":"membershipId","description":"Membership ID.","required":true,"type":"string","x-example":"[MEMBERSHIP_ID]","in":"path"},{"name":"payload","in":"body","schema":{"type":"object","properties":{"roles":{"type":"array","description":"Array of strings. Use this param to set the user roles in the team. A role can be any string. Learn more about [roles and permissions](\/docs\/permissions). Max length for each role is 32 chars.","default":null,"x-example":null,"items":{"type":"string"}}},"required":["roles"]}}]},"delete":{"summary":"Delete Team Membership","operationId":"teamsDeleteMembership","consumes":["application\/json"],"produces":[],"tags":["teams"],"description":"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 it is not accepted.","responses":{"204":{"description":"No content"}},"x-appwrite":{"method":"deleteMembership","weight":148,"cookies":false,"type":"","demo":"teams\/delete-membership.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/teams\/delete-team-membership.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"teams.write","platforms":["client","server","server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[],"JWT":[]}],"parameters":[{"name":"teamId","description":"Team unique ID.","required":true,"type":"string","x-example":"[TEAM_ID]","in":"path"},{"name":"membershipId","description":"Membership ID.","required":true,"type":"string","x-example":"[MEMBERSHIP_ID]","in":"path"}]}},"\/teams\/{teamId}\/memberships\/{membershipId}\/status":{"patch":{"summary":"Update Team Membership Status","operationId":"teamsUpdateMembershipStatus","consumes":["application\/json"],"produces":["application\/json"],"tags":["teams"],"description":"Use this endpoint to allow a user to accept an invitation to join a team after being redirected back to your app from the invitation email recieved by the user.","responses":{"200":{"description":"Membership","schema":{"$ref":"#\/definitions\/membership"}}},"x-appwrite":{"method":"updateMembershipStatus","weight":147,"cookies":false,"type":"","demo":"teams\/update-membership-status.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/teams\/update-team-membership-status.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"public","platforms":["client","server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"JWT":[]}],"parameters":[{"name":"teamId","description":"Team unique ID.","required":true,"type":"string","x-example":"[TEAM_ID]","in":"path"},{"name":"membershipId","description":"Membership ID.","required":true,"type":"string","x-example":"[MEMBERSHIP_ID]","in":"path"},{"name":"payload","in":"body","schema":{"type":"object","properties":{"userId":{"type":"string","description":"User unique ID.","default":null,"x-example":"[USER_ID]"},"secret":{"type":"string","description":"Secret key.","default":null,"x-example":"[SECRET]"}},"required":["userId","secret"]}}]}},"\/users":{"get":{"summary":"List Users","operationId":"usersList","consumes":["application\/json"],"produces":["application\/json"],"tags":["users"],"description":"Get a list of all the project's users. You can use the query params to filter your results.","responses":{"200":{"description":"Users List","schema":{"$ref":"#\/definitions\/userList"}}},"x-appwrite":{"method":"list","weight":150,"cookies":false,"type":"","demo":"users\/list.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/users\/list-users.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"users.read","platforms":["server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"search","description":"Search term to filter your list results. Max length: 256 chars.","required":false,"type":"string","x-example":"[SEARCH]","default":"","in":"query"},{"name":"limit","description":"Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.","required":false,"type":"integer","format":"int32","x-example":0,"default":25,"in":"query"},{"name":"offset","description":"Results offset. The default value is 0. Use this param to manage pagination.","required":false,"type":"integer","format":"int32","x-example":0,"default":0,"in":"query"},{"name":"orderType","description":"Order result by ASC or DESC order.","required":false,"type":"string","x-example":"ASC","default":"ASC","in":"query"}]},"post":{"summary":"Create User","operationId":"usersCreate","consumes":["application\/json"],"produces":["application\/json"],"tags":["users"],"description":"Create a new user.","responses":{"201":{"description":"User","schema":{"$ref":"#\/definitions\/user"}}},"x-appwrite":{"method":"create","weight":149,"cookies":false,"type":"","demo":"users\/create.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/users\/create-user.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"users.write","platforms":["server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"payload","in":"body","schema":{"type":"object","properties":{"email":{"type":"string","description":"User email.","default":null,"x-example":"email@example.com"},"password":{"type":"string","description":"User password. Must be between 6 to 32 chars.","default":null,"x-example":"password"},"name":{"type":"string","description":"User name. Max length: 128 chars.","default":"","x-example":"[NAME]"}},"required":["email","password"]}}]}},"\/users\/{userId}":{"get":{"summary":"Get User","operationId":"usersGet","consumes":["application\/json"],"produces":["application\/json"],"tags":["users"],"description":"Get a user by its unique ID.","responses":{"200":{"description":"User","schema":{"$ref":"#\/definitions\/user"}}},"x-appwrite":{"method":"get","weight":151,"cookies":false,"type":"","demo":"users\/get.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/users\/get-user.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"users.read","platforms":["server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"userId","description":"User unique ID.","required":true,"type":"string","in":"path"}]},"delete":{"summary":"Delete User","operationId":"usersDelete","consumes":["application\/json"],"produces":[],"tags":["users"],"description":"Delete a user by its unique ID.","responses":{"204":{"description":"No content"}},"x-appwrite":{"method":"delete","weight":160,"cookies":false,"type":"","demo":"users\/delete.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/users\/delete.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"users.write","platforms":["server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"userId","description":"User unique ID.","required":true,"type":"string","in":"path"}]}},"\/users\/{userId}\/logs":{"get":{"summary":"Get User Logs","operationId":"usersGetLogs","consumes":["application\/json"],"produces":["application\/json"],"tags":["users"],"description":"Get a user activity logs list by its unique ID.","responses":{"200":{"description":"Logs List","schema":{"$ref":"#\/definitions\/logList"}}},"x-appwrite":{"method":"getLogs","weight":154,"cookies":false,"type":"","demo":"users\/get-logs.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/users\/get-user-logs.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"users.read","platforms":["server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"userId","description":"User unique ID.","required":true,"type":"string","in":"path"}]}},"\/users\/{userId}\/prefs":{"get":{"summary":"Get User Preferences","operationId":"usersGetPrefs","consumes":["application\/json"],"produces":["application\/json"],"tags":["users"],"description":"Get the user preferences by its unique ID.","responses":{"200":{"description":"Preferences","schema":{"$ref":"#\/definitions\/preferences"}}},"x-appwrite":{"method":"getPrefs","weight":152,"cookies":false,"type":"","demo":"users\/get-prefs.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/users\/get-user-prefs.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"users.read","platforms":["server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"userId","description":"User unique ID.","required":true,"type":"string","in":"path"}]},"patch":{"summary":"Update User Preferences","operationId":"usersUpdatePrefs","consumes":["application\/json"],"produces":["application\/json"],"tags":["users"],"description":"Update the user preferences by its unique ID. You can pass only the specific settings you wish to update.","responses":{"200":{"description":"Preferences","schema":{"$ref":"#\/definitions\/preferences"}}},"x-appwrite":{"method":"updatePrefs","weight":157,"cookies":false,"type":"","demo":"users\/update-prefs.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/users\/update-user-prefs.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"users.write","platforms":["server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"userId","description":"User unique ID.","required":true,"type":"string","in":"path"},{"name":"payload","in":"body","schema":{"type":"object","properties":{"prefs":{"type":"object","description":"Prefs key-value JSON object.","default":null,"x-example":"{}"}},"required":["prefs"]}}]}},"\/users\/{userId}\/sessions":{"get":{"summary":"Get User Sessions","operationId":"usersGetSessions","consumes":["application\/json"],"produces":["application\/json"],"tags":["users"],"description":"Get the user sessions list by its unique ID.","responses":{"200":{"description":"Sessions List","schema":{"$ref":"#\/definitions\/sessionList"}}},"x-appwrite":{"method":"getSessions","weight":153,"cookies":false,"type":"","demo":"users\/get-sessions.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/users\/get-user-sessions.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"users.read","platforms":["server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"userId","description":"User unique ID.","required":true,"type":"string","in":"path"}]},"delete":{"summary":"Delete User Sessions","operationId":"usersDeleteSessions","consumes":["application\/json"],"produces":[],"tags":["users"],"description":"Delete all user's sessions by using the user's unique ID.","responses":{"204":{"description":"No content"}},"x-appwrite":{"method":"deleteSessions","weight":159,"cookies":false,"type":"","demo":"users\/delete-sessions.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/users\/delete-user-sessions.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"users.write","platforms":["server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"userId","description":"User unique ID.","required":true,"type":"string","in":"path"}]}},"\/users\/{userId}\/sessions\/{sessionId}":{"delete":{"summary":"Delete User Session","operationId":"usersDeleteSession","consumes":["application\/json"],"produces":[],"tags":["users"],"description":"Delete a user sessions by its unique ID.","responses":{"204":{"description":"No content"}},"x-appwrite":{"method":"deleteSession","weight":158,"cookies":false,"type":"","demo":"users\/delete-session.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/users\/delete-user-session.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"users.write","platforms":["server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"userId","description":"User unique ID.","required":true,"type":"string","in":"path"},{"name":"sessionId","description":"User unique session ID.","required":true,"type":"string","in":"path"}]}},"\/users\/{userId}\/status":{"patch":{"summary":"Update User Status","operationId":"usersUpdateStatus","consumes":["application\/json"],"produces":["application\/json"],"tags":["users"],"description":"Update the user status by its unique ID.","responses":{"200":{"description":"User","schema":{"$ref":"#\/definitions\/user"}}},"x-appwrite":{"method":"updateStatus","weight":155,"cookies":false,"type":"","demo":"users\/update-status.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/users\/update-user-status.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"users.write","platforms":["server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"userId","description":"User unique ID.","required":true,"type":"string","in":"path"},{"name":"payload","in":"body","schema":{"type":"object","properties":{"status":{"type":"integer","description":"User Status code. To activate the user pass 1, to block the user pass 2 and for disabling the user pass 0","default":null,"x-example":1}},"required":["status"]}}]}},"\/users\/{userId}\/verification":{"patch":{"summary":"Update Email Verification","operationId":"usersUpdateVerification","consumes":["application\/json"],"produces":["application\/json"],"tags":["users"],"description":"Update the user email verification status by its unique ID.","responses":{"200":{"description":"User","schema":{"$ref":"#\/definitions\/user"}}},"x-appwrite":{"method":"updateVerification","weight":156,"cookies":false,"type":"","demo":"users\/update-verification.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/users\/update-user-verification.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"users.write","platforms":["server"],"packaging":false,"auth":{"Project":[]}},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"userId","description":"User unique ID.","required":true,"type":"string","in":"path"},{"name":"payload","in":"body","schema":{"type":"object","properties":{"emailVerification":{"type":"boolean","description":"User Email Verification Status.","default":null,"x-example":false}},"required":["emailVerification"]}}]}}},"tags":[{"name":"account","description":"The Account service allows you to authenticate and manage a user account."},{"name":"avatars","description":"The Avatars service aims to help you complete everyday tasks related to your app image, icons, and avatars."},{"name":"database","description":"The Database service allows you to create structured collections of documents, query and filter lists of documents"},{"name":"locale","description":"The Locale service allows you to customize your app based on your users' location."},{"name":"health","description":"The Health service allows you to both validate and monitor your Appwrite server's health."},{"name":"projects","description":"The Project service allows you to manage all the projects in your Appwrite server."},{"name":"storage","description":"The Storage service allows you to manage your project files."},{"name":"teams","description":"The Teams service allows you to group users of your project and to enable them to share read and write access to your project resources"},{"name":"users","description":"The Users service allows you to manage your project users."},{"name":"functions","description":"The Functions Service allows you view, create and manage your Cloud Functions."}],"definitions":{"collectionList":{"description":"Collections List","type":"object","properties":{"sum":{"type":"integer","description":"Total sum of items in the list.","x-example":5,"format":"int32"},"collections":{"type":"array","description":"List of collections.","items":{"type":"object","$ref":"#\/definitions\/collection"},"x-example":""}},"required":["sum","collections"]},"documentList":{"description":"Documents List","type":"object","properties":{"sum":{"type":"integer","description":"Total sum of items in the list.","x-example":5,"format":"int32"},"documents":{"type":"array","description":"List of documents.","items":{"type":"object","$ref":"#\/definitions\/document"},"x-example":""}},"required":["sum","documents"]},"userList":{"description":"Users List","type":"object","properties":{"sum":{"type":"integer","description":"Total sum of items in the list.","x-example":5,"format":"int32"},"users":{"type":"array","description":"List of users.","items":{"type":"object","$ref":"#\/definitions\/user"},"x-example":""}},"required":["sum","users"]},"sessionList":{"description":"Sessions List","type":"object","properties":{"sum":{"type":"integer","description":"Total sum of items in the list.","x-example":5,"format":"int32"},"sessions":{"type":"array","description":"List of sessions.","items":{"type":"object","$ref":"#\/definitions\/session"},"x-example":""}},"required":["sum","sessions"]},"logList":{"description":"Logs List","type":"object","properties":{"logs":{"type":"array","description":"List of logs.","items":{"type":"object","$ref":"#\/definitions\/log"},"x-example":""}},"required":["logs"]},"fileList":{"description":"Files List","type":"object","properties":{"sum":{"type":"integer","description":"Total sum of items in the list.","x-example":5,"format":"int32"},"files":{"type":"array","description":"List of files.","items":{"type":"object","$ref":"#\/definitions\/file"},"x-example":""}},"required":["sum","files"]},"teamList":{"description":"Teams List","type":"object","properties":{"sum":{"type":"integer","description":"Total sum of items in the list.","x-example":5,"format":"int32"},"teams":{"type":"array","description":"List of teams.","items":{"type":"object","$ref":"#\/definitions\/team"},"x-example":""}},"required":["sum","teams"]},"membershipList":{"description":"Memberships List","type":"object","properties":{"sum":{"type":"integer","description":"Total sum of items in the list.","x-example":5,"format":"int32"},"memberships":{"type":"array","description":"List of memberships.","items":{"type":"object","$ref":"#\/definitions\/membership"},"x-example":""}},"required":["sum","memberships"]},"functionList":{"description":"Functions List","type":"object","properties":{"sum":{"type":"integer","description":"Total sum of items in the list.","x-example":5,"format":"int32"},"functions":{"type":"array","description":"List of functions.","items":{"type":"object","$ref":"#\/definitions\/function"},"x-example":""}},"required":["sum","functions"]},"tagList":{"description":"Tags List","type":"object","properties":{"sum":{"type":"integer","description":"Total sum of items in the list.","x-example":5,"format":"int32"},"tags":{"type":"array","description":"List of tags.","items":{"type":"object","$ref":"#\/definitions\/tag"},"x-example":""}},"required":["sum","tags"]},"executionList":{"description":"Executions List","type":"object","properties":{"sum":{"type":"integer","description":"Total sum of items in the list.","x-example":5,"format":"int32"},"executions":{"type":"array","description":"List of executions.","items":{"type":"object","$ref":"#\/definitions\/execution"},"x-example":""}},"required":["sum","executions"]},"projectList":{"description":"Projects List","type":"object","properties":{"sum":{"type":"integer","description":"Total sum of items in the list.","x-example":5,"format":"int32"},"projects":{"type":"array","description":"List of projects.","items":{"type":"object","$ref":"#\/definitions\/project"},"x-example":""}},"required":["sum","projects"]},"webhookList":{"description":"Webhooks List","type":"object","properties":{"sum":{"type":"integer","description":"Total sum of items in the list.","x-example":5,"format":"int32"},"webhooks":{"type":"array","description":"List of webhooks.","items":{"type":"object","$ref":"#\/definitions\/webhook"},"x-example":""}},"required":["sum","webhooks"]},"keyList":{"description":"API Keys List","type":"object","properties":{"sum":{"type":"integer","description":"Total sum of items in the list.","x-example":5,"format":"int32"},"keys":{"type":"array","description":"List of keys.","items":{"type":"object","$ref":"#\/definitions\/key"},"x-example":""}},"required":["sum","keys"]},"taskList":{"description":"Tasks List","type":"object","properties":{"sum":{"type":"integer","description":"Total sum of items in the list.","x-example":5,"format":"int32"},"tasks":{"type":"array","description":"List of tasks.","items":{"type":"object","$ref":"#\/definitions\/task"},"x-example":""}},"required":["sum","tasks"]},"platformList":{"description":"Platforms List","type":"object","properties":{"sum":{"type":"integer","description":"Total sum of items in the list.","x-example":5,"format":"int32"},"platforms":{"type":"array","description":"List of platforms.","items":{"type":"object","$ref":"#\/definitions\/platform"},"x-example":""}},"required":["sum","platforms"]},"domainList":{"description":"Domains List","type":"object","properties":{"sum":{"type":"integer","description":"Total sum of items in the list.","x-example":5,"format":"int32"},"domains":{"type":"array","description":"List of domains.","items":{"type":"object","$ref":"#\/definitions\/domain"},"x-example":""}},"required":["sum","domains"]},"countryList":{"description":"Countries List","type":"object","properties":{"sum":{"type":"integer","description":"Total sum of items in the list.","x-example":5,"format":"int32"},"countries":{"type":"array","description":"List of countries.","items":{"type":"object","$ref":"#\/definitions\/country"},"x-example":""}},"required":["sum","countries"]},"continentList":{"description":"Continents List","type":"object","properties":{"sum":{"type":"integer","description":"Total sum of items in the list.","x-example":5,"format":"int32"},"continents":{"type":"array","description":"List of continents.","items":{"type":"object","$ref":"#\/definitions\/continent"},"x-example":""}},"required":["sum","continents"]},"languageList":{"description":"Languages List","type":"object","properties":{"sum":{"type":"integer","description":"Total sum of items in the list.","x-example":5,"format":"int32"},"languages":{"type":"array","description":"List of languages.","items":{"type":"object","$ref":"#\/definitions\/language"},"x-example":""}},"required":["sum","languages"]},"currencyList":{"description":"Currencies List","type":"object","properties":{"sum":{"type":"integer","description":"Total sum of items in the list.","x-example":5,"format":"int32"},"currencies":{"type":"array","description":"List of currencies.","items":{"type":"object","$ref":"#\/definitions\/currency"},"x-example":""}},"required":["sum","currencies"]},"phoneList":{"description":"Phones List","type":"object","properties":{"sum":{"type":"integer","description":"Total sum of items in the list.","x-example":5,"format":"int32"},"phones":{"type":"array","description":"List of phones.","items":{"type":"object","$ref":"#\/definitions\/phone"},"x-example":""}},"required":["sum","phones"]},"permissions":{"description":"Permissions","type":"object","properties":{"read":{"type":"array","description":"Read permissions.","items":{"type":"string"},"x-example":"user:5e5ea5c16897e"},"write":{"type":"array","description":"Write permissions.","items":{"type":"string"},"x-example":"user:5e5ea5c16897e"}},"required":["read","write"]},"collection":{"description":"Collection","type":"object","properties":{"$id":{"type":"string","description":"Collection ID.","x-example":"5e5ea5c16897e"},"$permissions":{"type":"object","description":"Collection permissions.","x-example":[],"items":{"type":"object","$ref":"#\/definitions\/permissions"}},"name":{"type":"string","description":"Collection name.","x-example":"Movies"},"dateCreated":{"type":"integer","description":"Collection creation date in Unix timestamp.","x-example":1592981250,"format":"int32"},"dateUpdated":{"type":"integer","description":"Collection creation date in Unix timestamp.","x-example":1592981550,"format":"int32"},"rules":{"type":"array","description":"Collection rules.","items":{"type":"object","$ref":"#\/definitions\/rule"},"x-example":""}},"required":["$id","$permissions","name","dateCreated","dateUpdated","rules"]},"document":{"description":"Document","type":"object","properties":{"$id":{"type":"string","description":"Document ID.","x-example":"5e5ea5c16897e"},"$collection":{"type":"string","description":"Collection ID.","x-example":"5e5ea5c15117e"},"$permissions":{"type":"object","description":"Document permissions.","x-example":[],"items":{"type":"object","$ref":"#\/definitions\/permissions"}}},"additionalProperties":true,"required":["$id","$collection","$permissions"]},"rule":{"description":"Rule","type":"object","properties":{"$id":{"type":"string","description":"Rule ID.","x-example":"5e5ea5c16897e"},"$collection":{"type":"string","description":"Rule Collection.","x-example":"5e5e66c16897e"},"type":{"type":"string","description":"Rule type. Possible values: ","x-example":"title"},"key":{"type":"string","description":"Rule key.","x-example":"title"},"label":{"type":"string","description":"Rule label.","x-example":"Title"},"default":{"type":"string","description":"Rule default value.","x-example":"Movie Name"},"array":{"type":"boolean","description":"Is array?","x-example":false},"required":{"type":"boolean","description":"Is required?","x-example":true},"list":{"type":"array","description":"List of allowed values","items":{"type":"string"},"x-example":"5e5ea5c168099"}},"required":["$id","$collection","type","key","label","default","array","required","list"]},"log":{"description":"Log","type":"object","properties":{"event":{"type":"string","description":"Event name.","x-example":"account.sessions.create"},"ip":{"type":"string","description":"IP session in use when the session was created.","x-example":"127.0.0.1"},"time":{"type":"integer","description":"Log creation time in Unix timestamp.","x-example":1592981250,"format":"int32"},"osCode":{"type":"string","description":"Operating system code name. View list of [available options](https:\/\/github.com\/appwrite\/appwrite\/blob\/master\/docs\/lists\/os.json).","x-example":"Mac"},"osName":{"type":"string","description":"Operating system name.","x-example":"Mac"},"osVersion":{"type":"string","description":"Operating system version.","x-example":"Mac"},"clientType":{"type":"string","description":"Client type.","x-example":"browser"},"clientCode":{"type":"string","description":"Client code name. View list of [available options](https:\/\/github.com\/appwrite\/appwrite\/blob\/master\/docs\/lists\/clients.json).","x-example":"CM"},"clientName":{"type":"string","description":"Client name.","x-example":"Chrome Mobile iOS"},"clientVersion":{"type":"string","description":"Client version.","x-example":"84.0"},"clientEngine":{"type":"string","description":"Client engine name.","x-example":"WebKit"},"clientEngineVersion":{"type":"string","description":"Client engine name.","x-example":"605.1.15"},"deviceName":{"type":"string","description":"Device name.","x-example":"smartphone"},"deviceBrand":{"type":"string","description":"Device brand name.","x-example":"Google"},"deviceModel":{"type":"string","description":"Device model name.","x-example":"Nexus 5"},"countryCode":{"type":"string","description":"Country two-character ISO 3166-1 alpha code.","x-example":"US"},"countryName":{"type":"string","description":"Country name.","x-example":"United States"}},"required":["event","ip","time","osCode","osName","osVersion","clientType","clientCode","clientName","clientVersion","clientEngine","clientEngineVersion","deviceName","deviceBrand","deviceModel","countryCode","countryName"]},"user":{"description":"User","type":"object","properties":{"$id":{"type":"string","description":"User ID.","x-example":"5e5ea5c16897e"},"name":{"type":"string","description":"User name.","x-example":"John Doe"},"registration":{"type":"integer","description":"User registration date in Unix timestamp.","x-example":1592981250,"format":"int32"},"status":{"type":"integer","description":"User status. 0 for Unactivated, 1 for active and 2 is blocked.","x-example":0,"format":"int32"},"passwordUpdate":{"type":"integer","description":"Unix timestamp of the most recent password update","x-example":1592981250,"format":"int32"},"email":{"type":"string","description":"User email address.","x-example":"john@appwrite.io"},"emailVerification":{"type":"boolean","description":"Email verification status.","x-example":true},"prefs":{"type":"object","description":"User preferences as a key-value object","x-example":{"theme":"pink","timezone":"UTC"},"items":{"type":"object","$ref":"#\/definitions\/preferences"}}},"required":["$id","name","registration","status","passwordUpdate","email","emailVerification","prefs"]},"preferences":{"description":"Preferences","type":"object","additionalProperties":true},"session":{"description":"Session","type":"object","properties":{"$id":{"type":"string","description":"Session ID.","x-example":"5e5ea5c16897e"},"userId":{"type":"string","description":"User ID.","x-example":"5e5bb8c16897e"},"expire":{"type":"integer","description":"Session expiration date in Unix timestamp.","x-example":1592981250,"format":"int32"},"provider":{"type":"string","description":"Session Provider.","x-example":"email"},"providerUid":{"type":"string","description":"Session Provider User ID.","x-example":"user@example.com"},"providerToken":{"type":"string","description":"Session Provider Token.","x-example":"MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI3"},"ip":{"type":"string","description":"IP in use when the session was created.","x-example":"127.0.0.1"},"osCode":{"type":"string","description":"Operating system code name. View list of [available options](https:\/\/github.com\/appwrite\/appwrite\/blob\/master\/docs\/lists\/os.json).","x-example":"Mac"},"osName":{"type":"string","description":"Operating system name.","x-example":"Mac"},"osVersion":{"type":"string","description":"Operating system version.","x-example":"Mac"},"clientType":{"type":"string","description":"Client type.","x-example":"browser"},"clientCode":{"type":"string","description":"Client code name. View list of [available options](https:\/\/github.com\/appwrite\/appwrite\/blob\/master\/docs\/lists\/clients.json).","x-example":"CM"},"clientName":{"type":"string","description":"Client name.","x-example":"Chrome Mobile iOS"},"clientVersion":{"type":"string","description":"Client version.","x-example":"84.0"},"clientEngine":{"type":"string","description":"Client engine name.","x-example":"WebKit"},"clientEngineVersion":{"type":"string","description":"Client engine name.","x-example":"605.1.15"},"deviceName":{"type":"string","description":"Device name.","x-example":"smartphone"},"deviceBrand":{"type":"string","description":"Device brand name.","x-example":"Google"},"deviceModel":{"type":"string","description":"Device model name.","x-example":"Nexus 5"},"countryCode":{"type":"string","description":"Country two-character ISO 3166-1 alpha code.","x-example":"US"},"countryName":{"type":"string","description":"Country name.","x-example":"United States"},"current":{"type":"boolean","description":"Returns true if this the current user session.","x-example":true}},"required":["$id","userId","expire","provider","providerUid","providerToken","ip","osCode","osName","osVersion","clientType","clientCode","clientName","clientVersion","clientEngine","clientEngineVersion","deviceName","deviceBrand","deviceModel","countryCode","countryName","current"]},"token":{"description":"Token","type":"object","properties":{"$id":{"type":"string","description":"Token ID.","x-example":"bb8ea5c16897e"},"userId":{"type":"string","description":"User ID.","x-example":"5e5ea5c168bb8"},"secret":{"type":"string","description":"Token secret key. This will return an empty string unless the response is returned using an API key or as part of a webhook payload.","x-example":""},"expire":{"type":"integer","description":"Token expiration date in Unix timestamp.","x-example":1592981250,"format":"int32"}},"required":["$id","userId","secret","expire"]},"jwt":{"description":"JWT","type":"object","properties":{"jwt":{"type":"string","description":"JWT encoded string.","x-example":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"}},"required":["jwt"]},"locale":{"description":"Locale","type":"object","properties":{"ip":{"type":"string","description":"User IP address.","x-example":"127.0.0.1"},"countryCode":{"type":"string","description":"Country code in [ISO 3166-1](http:\/\/en.wikipedia.org\/wiki\/ISO_3166-1) two-character format","x-example":"US"},"country":{"type":"string","description":"Country name. This field support localization.","x-example":"United States"},"continentCode":{"type":"string","description":"Continent code. A two character continent code \"AF\" for Africa, \"AN\" for Antarctica, \"AS\" for Asia, \"EU\" for Europe, \"NA\" for North America, \"OC\" for Oceania, and \"SA\" for South America.","x-example":"NA"},"continent":{"type":"string","description":"Continent name. This field support localization.","x-example":"North America"},"eu":{"type":"boolean","description":"True if country is part of the Europian Union.","x-example":false},"currency":{"type":"string","description":"Currency code in [ISO 4217-1](http:\/\/en.wikipedia.org\/wiki\/ISO_4217) three-character format","x-example":"USD"}},"required":["ip","countryCode","country","continentCode","continent","eu","currency"]},"file":{"description":"File","type":"object","properties":{"$id":{"type":"string","description":"File ID.","x-example":"5e5ea5c16897e"},"$read":{"type":"array","description":"File read permissions.","items":{"type":"string"},"x-example":["role:all"]},"$write":{"type":"array","description":"File write permissions.","items":{"type":"string"},"x-example":["user:608f9da25e7e1"]},"name":{"type":"string","description":"File name.","x-example":"Pink.png"},"dateCreated":{"type":"integer","description":"File creation date in Unix timestamp.","x-example":1592981250,"format":"int32"},"signature":{"type":"string","description":"File MD5 signature.","x-example":"5d529fd02b544198ae075bd57c1762bb"},"mimeType":{"type":"string","description":"File mime type.","x-example":"image\/png"},"sizeOriginal":{"type":"integer","description":"File original size in bytes.","x-example":17890,"format":"int32"}},"required":["$id","$read","$write","name","dateCreated","signature","mimeType","sizeOriginal"]},"team":{"description":"Team","type":"object","properties":{"$id":{"type":"string","description":"Team ID.","x-example":"5e5ea5c16897e"},"name":{"type":"string","description":"Team name.","x-example":"VIP"},"dateCreated":{"type":"integer","description":"Team creation date in Unix timestamp.","x-example":1592981250,"format":"int32"},"sum":{"type":"integer","description":"Total sum of team members.","x-example":7,"format":"int32"}},"required":["$id","name","dateCreated","sum"]},"membership":{"description":"Membership","type":"object","properties":{"$id":{"type":"string","description":"Membership ID.","x-example":"5e5ea5c16897e"},"userId":{"type":"string","description":"User ID.","x-example":"5e5ea5c16897e"},"teamId":{"type":"string","description":"Team ID.","x-example":"5e5ea5c16897e"},"name":{"type":"string","description":"User name.","x-example":"VIP"},"email":{"type":"string","description":"User email address.","x-example":"john@appwrite.io"},"invited":{"type":"integer","description":"Date, the user has been invited to join the team in Unix timestamp.","x-example":1592981250,"format":"int32"},"joined":{"type":"integer","description":"Date, the user has accepted the invitation to join the team in Unix timestamp.","x-example":1592981250,"format":"int32"},"confirm":{"type":"boolean","description":"User confirmation status, true if the user has joined the team or false otherwise.","x-example":false},"roles":{"type":"array","description":"User list of roles","items":{"type":"string"},"x-example":"admin"}},"required":["$id","userId","teamId","name","email","invited","joined","confirm","roles"]},"function":{"description":"Function","type":"object","properties":{"$id":{"type":"string","description":"Function ID.","x-example":"5e5ea5c16897e"},"$permissions":{"type":"object","description":"Function permissions.","x-example":[],"items":{"type":"object","$ref":"#\/definitions\/permissions"}},"name":{"type":"string","description":"Function name.","x-example":"My Function"},"dateCreated":{"type":"integer","description":"Function creation date in Unix timestamp.","x-example":1592981250,"format":"int32"},"dateUpdated":{"type":"integer","description":"Function update date in Unix timestamp.","x-example":1592981257,"format":"int32"},"status":{"type":"string","description":"Function status. Possible values: disabled, enabled","x-example":"enabled"},"env":{"type":"string","description":"Function execution environment.","x-example":"python-3.8"},"tag":{"type":"string","description":"Function active tag ID.","x-example":"5e5ea5c16897e"},"vars":{"type":"string","description":"Function environment variables.","x-example":{"key":"value"}},"events":{"type":"array","description":"Function trigger events.","items":{"type":"string"},"x-example":"account.create"},"schedule":{"type":"string","description":"Function execution schedult in CRON format.","x-example":"5 4 * * *"},"scheduleNext":{"type":"integer","description":"Function next scheduled execution date in Unix timestamp.","x-example":1592981292,"format":"int32"},"schedulePrevious":{"type":"integer","description":"Function next scheduled execution date in Unix timestamp.","x-example":1592981237,"format":"int32"},"timeout":{"type":"integer","description":"Function execution timeout in seconds.","x-example":1592981237,"format":"int32"}},"required":["$id","$permissions","name","dateCreated","dateUpdated","status","env","tag","vars","events","schedule","scheduleNext","schedulePrevious","timeout"]},"tag":{"description":"Tag","type":"object","properties":{"$id":{"type":"string","description":"Tag ID.","x-example":"5e5ea5c16897e"},"functionId":{"type":"string","description":"Function ID.","x-example":"5e5ea6g16897e"},"dateCreated":{"type":"integer","description":"The tag creation date in Unix timestamp.","x-example":1592981250,"format":"int32"},"command":{"type":"string","description":"The entrypoint command in use to execute the tag code.","x-example":"enabled"},"size":{"type":"string","description":"The code size in bytes.","x-example":"python-3.8"}},"required":["$id","functionId","dateCreated","command","size"]},"execution":{"description":"Execution","type":"object","properties":{"$id":{"type":"string","description":"Execution ID.","x-example":"5e5ea5c16897e"},"functionId":{"type":"string","description":"Function ID.","x-example":"5e5ea6g16897e"},"dateCreated":{"type":"integer","description":"The execution creation date in Unix timestamp.","x-example":1592981250,"format":"int32"},"trigger":{"type":"string","description":"The trigger that caused the function to execute. Possible values can be: `http`, `schedule`, or `event`.","x-example":"http"},"status":{"type":"string","description":"The status of the function execution. Possible values can be: `waiting`, `processing`, `completed`, or `failed`.","x-example":"processing"},"exitCode":{"type":"integer","description":"The script exit code.","x-example":0,"format":"int32"},"stdout":{"type":"string","description":"The script stdout output string.","x-example":""},"stderr":{"type":"string","description":"The script stderr output string.","x-example":""},"time":{"type":"number","description":"The script execution time in seconds.","x-example":0.4,"format":"float"}},"required":["$id","functionId","dateCreated","trigger","status","exitCode","stdout","stderr","time"]},"project":{"description":"Project","type":"object","properties":{"$id":{"type":"string","description":"Project ID.","x-example":"5e5ea5c16897e"},"name":{"type":"string","description":"Project name.","x-example":"New Project"},"description":{"type":"string","description":"Project description.","x-example":"This is a new project."},"teamId":{"type":"string","description":"Project team ID.","x-example":"1592981250"},"logo":{"type":"string","description":"Project logo file ID.","x-example":"5f5c451b403cb"},"url":{"type":"string","description":"Project website URL.","x-example":"5f5c451b403cb"},"legalName":{"type":"string","description":"Company legal name.","x-example":"Company LTD."},"legalCountry":{"type":"string","description":"Country code in [ISO 3166-1](http:\/\/en.wikipedia.org\/wiki\/ISO_3166-1) two-character format.","x-example":"US"},"legalState":{"type":"string","description":"State name.","x-example":"New York"},"legalCity":{"type":"string","description":"City name.","x-example":"New York City."},"legalAddress":{"type":"string","description":"Company Address.","x-example":"620 Eighth Avenue, New York, NY 10018"},"legalTaxId":{"type":"string","description":"Company Tax ID.","x-example":"131102020"},"usersAuthLimit":{"type":"integer","description":"Max users allowed. 0 is unlimited.","x-example":100,"format":"int32"},"platforms":{"type":"array","description":"List of Platforms.","items":{"type":"object","$ref":"#\/definitions\/platform"},"x-example":[]},"webhooks":{"type":"array","description":"List of Webhooks.","items":{"type":"object","$ref":"#\/definitions\/webhook"},"x-example":[]},"keys":{"type":"array","description":"List of API Keys.","items":{"type":"object","$ref":"#\/definitions\/key"},"x-example":[]},"domains":{"type":"array","description":"List of Domains.","items":{"type":"object","$ref":"#\/definitions\/domain"},"x-example":[]},"tasks":{"type":"array","description":"List of Tasks.","items":{"type":"object","$ref":"#\/definitions\/task"},"x-example":[]},"usersOauth2AmazonAppid":{"type":"string","description":"Amazon OAuth app ID.","x-example":"123247283472834787438"},"usersOauth2AmazonSecret":{"type":"string","description":"Amazon OAuth secret ID.","x-example":"djsgudsdsewe43434343dd34..."},"usersOauth2AppleAppid":{"type":"string","description":"Apple OAuth app ID.","x-example":"123247283472834787438"},"usersOauth2AppleSecret":{"type":"string","description":"Apple OAuth secret ID.","x-example":"djsgudsdsewe43434343dd34..."},"usersOauth2BitbucketAppid":{"type":"string","description":"BitBucket OAuth app ID.","x-example":"123247283472834787438"},"usersOauth2BitbucketSecret":{"type":"string","description":"BitBucket OAuth secret ID.","x-example":"djsgudsdsewe43434343dd34..."},"usersOauth2BitlyAppid":{"type":"string","description":"Bitly OAuth app ID.","x-example":"123247283472834787438"},"usersOauth2BitlySecret":{"type":"string","description":"Bitly OAuth secret ID.","x-example":"djsgudsdsewe43434343dd34..."},"usersOauth2BoxAppid":{"type":"string","description":"Box OAuth app ID.","x-example":"123247283472834787438"},"usersOauth2BoxSecret":{"type":"string","description":"Box OAuth secret ID.","x-example":"djsgudsdsewe43434343dd34..."},"usersOauth2DiscordAppid":{"type":"string","description":"Discord OAuth app ID.","x-example":"123247283472834787438"},"usersOauth2DiscordSecret":{"type":"string","description":"Discord OAuth secret ID.","x-example":"djsgudsdsewe43434343dd34..."},"usersOauth2DropboxAppid":{"type":"string","description":"Dropbox OAuth app ID.","x-example":"123247283472834787438"},"usersOauth2DropboxSecret":{"type":"string","description":"Dropbox OAuth secret ID.","x-example":"djsgudsdsewe43434343dd34..."},"usersOauth2FacebookAppid":{"type":"string","description":"Facebook OAuth app ID.","x-example":"123247283472834787438"},"usersOauth2FacebookSecret":{"type":"string","description":"Facebook OAuth secret ID.","x-example":"djsgudsdsewe43434343dd34..."},"usersOauth2GithubAppid":{"type":"string","description":"GitHub OAuth app ID.","x-example":"123247283472834787438"},"usersOauth2GithubSecret":{"type":"string","description":"GitHub OAuth secret ID.","x-example":"djsgudsdsewe43434343dd34..."},"usersOauth2GitlabAppid":{"type":"string","description":"GitLab OAuth app ID.","x-example":"123247283472834787438"},"usersOauth2GitlabSecret":{"type":"string","description":"GitLab OAuth secret ID.","x-example":"djsgudsdsewe43434343dd34..."},"usersOauth2GoogleAppid":{"type":"string","description":"Google OAuth app ID.","x-example":"123247283472834787438"},"usersOauth2GoogleSecret":{"type":"string","description":"Google OAuth secret ID.","x-example":"djsgudsdsewe43434343dd34..."},"usersOauth2LinkedinAppid":{"type":"string","description":"LinkedIn OAuth app ID.","x-example":"123247283472834787438"},"usersOauth2LinkedinSecret":{"type":"string","description":"LinkedIn OAuth secret ID.","x-example":"djsgudsdsewe43434343dd34..."},"usersOauth2MicrosoftAppid":{"type":"string","description":"Microsoft OAuth app ID.","x-example":"123247283472834787438"},"usersOauth2MicrosoftSecret":{"type":"string","description":"Microsoft OAuth secret ID.","x-example":"djsgudsdsewe43434343dd34..."},"usersOauth2PaypalAppid":{"type":"string","description":"PayPal OAuth app ID.","x-example":"123247283472834787438"},"usersOauth2PaypalSecret":{"type":"string","description":"PayPal OAuth secret ID.","x-example":"djsgudsdsewe43434343dd34..."},"usersOauth2PaypalSandboxAppid":{"type":"string","description":"PayPal OAuth app ID.","x-example":"123247283472834787438"},"usersOauth2PaypalSandboxSecret":{"type":"string","description":"PayPal OAuth secret ID.","x-example":"djsgudsdsewe43434343dd34..."},"usersOauth2SalesforceAppid":{"type":"string","description":"Salesforce OAuth app ID.","x-example":"123247283472834787438"},"usersOauth2SalesforceSecret":{"type":"string","description":"Salesforce OAuth secret ID.","x-example":"djsgudsdsewe43434343dd34..."},"usersOauth2SlackAppid":{"type":"string","description":"Slack OAuth app ID.","x-example":"123247283472834787438"},"usersOauth2SlackSecret":{"type":"string","description":"Slack OAuth secret ID.","x-example":"djsgudsdsewe43434343dd34..."},"usersOauth2SpotifyAppid":{"type":"string","description":"Spotify OAuth app ID.","x-example":"123247283472834787438"},"usersOauth2SpotifySecret":{"type":"string","description":"Spotify OAuth secret ID.","x-example":"djsgudsdsewe43434343dd34..."},"usersOauth2TradeshiftAppid":{"type":"string","description":"Tradeshift OAuth app ID.","x-example":"123247283472834787438"},"usersOauth2TradeshiftSecret":{"type":"string","description":"Tradeshift OAuth secret ID.","x-example":"djsgudsdsewe43434343dd34..."},"usersOauth2TradeshiftBoxAppid":{"type":"string","description":"Tradeshift OAuth app ID.","x-example":"123247283472834787438"},"usersOauth2TradeshiftBoxSecret":{"type":"string","description":"Tradeshift OAuth secret ID.","x-example":"djsgudsdsewe43434343dd34..."},"usersOauth2TwitchAppid":{"type":"string","description":"Twitch OAuth app ID.","x-example":"123247283472834787438"},"usersOauth2TwitchSecret":{"type":"string","description":"Twitch OAuth secret ID.","x-example":"djsgudsdsewe43434343dd34..."},"usersOauth2VkAppid":{"type":"string","description":"VK OAuth app ID.","x-example":"123247283472834787438"},"usersOauth2VkSecret":{"type":"string","description":"VK OAuth secret ID.","x-example":"djsgudsdsewe43434343dd34..."},"usersOauth2YahooAppid":{"type":"string","description":"Yahoo OAuth app ID.","x-example":"123247283472834787438"},"usersOauth2YahooSecret":{"type":"string","description":"Yahoo OAuth secret ID.","x-example":"djsgudsdsewe43434343dd34..."},"usersOauth2YandexAppid":{"type":"string","description":"Yandex OAuth app ID.","x-example":"123247283472834787438"},"usersOauth2YandexSecret":{"type":"string","description":"Yandex OAuth secret ID.","x-example":"djsgudsdsewe43434343dd34..."},"usersOauth2WordpressAppid":{"type":"string","description":"WordPress OAuth app ID.","x-example":"123247283472834787438"},"usersOauth2WordpressSecret":{"type":"string","description":"WordPress OAuth secret ID.","x-example":"djsgudsdsewe43434343dd34..."},"usersOauth2MockAppid":{"type":"string","description":"Mock OAuth app ID.","x-example":"123247283472834787438"},"usersOauth2MockSecret":{"type":"string","description":"Mock OAuth secret ID.","x-example":"djsgudsdsewe43434343dd34..."},"usersAuthEmailPassword":{"type":"boolean","description":"Email\/Password auth method status","x-example":true},"usersAuthAnonymous":{"type":"boolean","description":"Anonymous auth method status","x-example":true},"usersAuthInvites":{"type":"boolean","description":"Invites auth method status","x-example":true},"usersAuthJWT":{"type":"boolean","description":"JWT auth method status","x-example":true},"usersAuthPhone":{"type":"boolean","description":"Phone auth method status","x-example":true}},"required":["$id","name","description","teamId","logo","url","legalName","legalCountry","legalState","legalCity","legalAddress","legalTaxId","usersAuthLimit","platforms","webhooks","keys","domains","tasks","usersOauth2AmazonAppid","usersOauth2AmazonSecret","usersOauth2AppleAppid","usersOauth2AppleSecret","usersOauth2BitbucketAppid","usersOauth2BitbucketSecret","usersOauth2BitlyAppid","usersOauth2BitlySecret","usersOauth2BoxAppid","usersOauth2BoxSecret","usersOauth2DiscordAppid","usersOauth2DiscordSecret","usersOauth2DropboxAppid","usersOauth2DropboxSecret","usersOauth2FacebookAppid","usersOauth2FacebookSecret","usersOauth2GithubAppid","usersOauth2GithubSecret","usersOauth2GitlabAppid","usersOauth2GitlabSecret","usersOauth2GoogleAppid","usersOauth2GoogleSecret","usersOauth2LinkedinAppid","usersOauth2LinkedinSecret","usersOauth2MicrosoftAppid","usersOauth2MicrosoftSecret","usersOauth2PaypalAppid","usersOauth2PaypalSecret","usersOauth2PaypalSandboxAppid","usersOauth2PaypalSandboxSecret","usersOauth2SalesforceAppid","usersOauth2SalesforceSecret","usersOauth2SlackAppid","usersOauth2SlackSecret","usersOauth2SpotifyAppid","usersOauth2SpotifySecret","usersOauth2TradeshiftAppid","usersOauth2TradeshiftSecret","usersOauth2TradeshiftBoxAppid","usersOauth2TradeshiftBoxSecret","usersOauth2TwitchAppid","usersOauth2TwitchSecret","usersOauth2VkAppid","usersOauth2VkSecret","usersOauth2YahooAppid","usersOauth2YahooSecret","usersOauth2YandexAppid","usersOauth2YandexSecret","usersOauth2WordpressAppid","usersOauth2WordpressSecret","usersOauth2MockAppid","usersOauth2MockSecret","usersAuthEmailPassword","usersAuthAnonymous","usersAuthInvites","usersAuthJWT","usersAuthPhone"]},"webhook":{"description":"Webhook","type":"object","properties":{"$id":{"type":"string","description":"Webhook ID.","x-example":"5e5ea5c16897e"},"name":{"type":"string","description":"Webhook name.","x-example":"My Webhook"},"url":{"type":"string","description":"Webhook URL endpoint.","x-example":"https:\/\/example.com\/webhook"},"events":{"type":"array","description":"Webhook trigger events.","items":{"type":"string"},"x-example":"database.collections.update"},"security":{"type":"boolean","description":"Indicated if SSL \/ TLS Certificate verification is enabled.","x-example":true},"httpUser":{"type":"string","description":"HTTP basic authentication username.","x-example":"username"},"httpPass":{"type":"string","description":"HTTP basic authentication password.","x-example":"password"}},"required":["$id","name","url","events","security","httpUser","httpPass"]},"key":{"description":"Key","type":"object","properties":{"$id":{"type":"string","description":"Key ID.","x-example":"5e5ea5c16897e"},"name":{"type":"string","description":"Key name.","x-example":"My API Key"},"scopes":{"type":"array","description":"Allowed permission scopes.","items":{"type":"string"},"x-example":"users.read"},"secret":{"type":"string","description":"Secret key.","x-example":"919c2d18fb5d4...a2ae413da83346ad2"}},"required":["$id","name","scopes","secret"]},"task":{"description":"Task","type":"object","properties":{"$id":{"type":"string","description":"Task ID.","x-example":"5e5ea5c16897e"},"name":{"type":"string","description":"Task name.","x-example":"My Task"},"security":{"type":"boolean","description":"Indicated if SSL \/ TLS Certificate verification is enabled.","x-example":true},"httpMethod":{"type":"string","description":"Task HTTP Method.","x-example":"POST"},"httpUrl":{"type":"string","description":"Task HTTP URL.","x-example":"https:\/\/example.com\/task"},"httpHeaders":{"type":"array","description":"Task HTTP headers.","items":{"type":"string"},"x-example":"key:value"},"httpUser":{"type":"string","description":"HTTP basic authentication username.","x-example":"username"},"httpPass":{"type":"string","description":"HTTP basic authentication password.","x-example":"password"},"duration":{"type":"number","description":"Task duration in seconds.","x-example":1.2,"format":"float"},"delay":{"type":"number","description":"Task delay time in seconds.","x-example":1.2,"format":"float"},"failures":{"type":"integer","description":"Number of recurring task failures.","x-example":0,"format":"int32"},"schedule":{"type":"string","description":"Task schedule in CRON syntax.","x-example":"* * * * *"},"status":{"type":"string","description":"Task status. Possible values: play, pause","x-example":"enabled"},"updated":{"type":"integer","description":"Task last updated time in Unix timestamp.","x-example":1592981250,"format":"int32"},"previous":{"type":"integer","description":"Task previous run time in Unix timestamp.","x-example":1592981250,"format":"int32"},"next":{"type":"integer","description":"Task next run time in Unix timestamp.","x-example":1592981650,"format":"int32"}},"required":["$id","name","security","httpMethod","httpUrl","httpHeaders","httpUser","httpPass","duration","delay","failures","schedule","status","updated","previous","next"]},"domain":{"description":"Domain","type":"object","properties":{"$id":{"type":"string","description":"Domain ID.","x-example":"5e5ea5c16897e"},"domain":{"type":"string","description":"Domain name.","x-example":"appwrite.company.com"},"registerable":{"type":"string","description":"Registerable domain name.","x-example":"company.com"},"tld":{"type":"string","description":"TLD name.","x-example":"com"},"verification":{"type":"boolean","description":"Verification process status.","x-example":true},"certificateId":{"type":"string","description":"Certificate ID.","x-example":"6ejea5c13377e"}},"required":["$id","domain","registerable","tld","verification","certificateId"]},"platform":{"description":"Platform","type":"object","properties":{"$id":{"type":"string","description":"Platform ID.","x-example":"5e5ea5c16897e"},"name":{"type":"string","description":"Platform name.","x-example":"My Web App"},"type":{"type":"string","description":"Platform type. Possible values are: web, flutter-ios, flutter-android, ios, android, and unity.","x-example":"My Web App"},"key":{"type":"string","description":"Platform Key. iOS bundle ID or Android package name. Empty string for other platforms.","x-example":"com.company.appname"},"store":{"type":"string","description":"App store or Google Play store ID.","x-example":""},"hostname":{"type":"string","description":"Web app hostname. Empty string for other platforms.","x-example":true},"httpUser":{"type":"string","description":"HTTP basic authentication username.","x-example":"username"},"httpPass":{"type":"string","description":"HTTP basic authentication password.","x-example":"password"}},"required":["$id","name","type","key","store","hostname","httpUser","httpPass"]},"country":{"description":"Country","type":"object","properties":{"name":{"type":"string","description":"Country name.","x-example":"United States"},"code":{"type":"string","description":"Country two-character ISO 3166-1 alpha code.","x-example":"US"}},"required":["name","code"]},"continent":{"description":"Continent","type":"object","properties":{"name":{"type":"string","description":"Continent name.","x-example":"Europe"},"code":{"type":"string","description":"Continent two letter code.","x-example":"EU"}},"required":["name","code"]},"language":{"description":"Language","type":"object","properties":{"name":{"type":"string","description":"Language name.","x-example":"Italian"},"code":{"type":"string","description":"Language two-character ISO 639-1 codes.","x-example":"it"},"nativeName":{"type":"string","description":"Language native name.","x-example":"Italiano"}},"required":["name","code","nativeName"]},"currency":{"description":"Currency","type":"object","properties":{"symbol":{"type":"string","description":"Currency symbol.","x-example":"$"},"name":{"type":"string","description":"Currency name.","x-example":"US dollar"},"symbolNative":{"type":"string","description":"Currency native symbol.","x-example":"$"},"decimalDigits":{"type":"integer","description":"Number of decimal digits.","x-example":2,"format":"int32"},"rounding":{"type":"number","description":"Currency digit rounding.","x-example":0,"format":"float"},"code":{"type":"string","description":"Currency code in [ISO 4217-1](http:\/\/en.wikipedia.org\/wiki\/ISO_4217) three-character format.","x-example":"USD"},"namePlural":{"type":"string","description":"Currency plural name","x-example":"US dollars"}},"required":["symbol","name","symbolNative","decimalDigits","rounding","code","namePlural"]},"phone":{"description":"Phone","type":"object","properties":{"code":{"type":"string","description":"Phone code.","x-example":"+1"},"countryCode":{"type":"string","description":"Country two-character ISO 3166-1 alpha code.","x-example":"US"},"countryName":{"type":"string","description":"Country name.","x-example":"United States"}},"required":["code","countryCode","countryName"]}},"externalDocs":{"description":"Full API docs, specs and tutorials","url":"https:\/\/appwrite.io\/docs"}} \ No newline at end of file