diff --git a/.env b/.env index 9f6050fe50..87834e19eb 100644 --- a/.env +++ b/.env @@ -18,7 +18,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/.travis.yml b/.travis.yml index af63bd6030..a1ceb96cc3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -34,8 +34,9 @@ install: - sleep 10 script: -- docker ps +- 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 diff --git a/Dockerfile b/Dockerfile index 00958f3f14..09cbd677a1 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 && \ @@ -165,6 +174,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/config/collections.php b/app/config/collections.php index 3b28dd368d..25023cbcc6 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' => [ @@ -1530,7 +1530,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' => [ @@ -1648,7 +1648,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' => [ @@ -1702,7 +1702,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' => [ @@ -1792,7 +1792,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/config/collections2.php b/app/config/collections2.php new file mode 100644 index 0000000000..b130092886 --- /dev/null +++ b/app/config/collections2.php @@ -0,0 +1,1016 @@ + [ + '$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' => [ + [ + '$id' => '_fulltext_name', + 'type' => Database::INDEX_FULLTEXT, + 'attributes' => ['name'], + 'lengths' => [1024], + 'orders' => [Database::ORDER_ASC], + ] + ], + ], + + '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' => 16384, + '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' => 16384, + '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' => 16384, + 'signed' => true, + 'required' => false, + 'array' => true, + 'filters' => ['json'], + ], + [ + '$id' => 'tokens', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 16384, + 'signed' => true, + 'required' => false, + 'array' => true, + 'filters' => ['json'], + ], + [ + '$id' => 'memberships', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 16384, + 'signed' => true, + 'required' => false, + 'array' => true, + 'filters' => ['json'], + ], + ], + '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' => [ + [ + '$id' => '_fulltext_name', + 'type' => Database::INDEX_FULLTEXT, + 'attributes' => ['name'], + 'lengths' => [1024], + 'orders' => [Database::ORDER_ASC], + ] + ], + ], + + '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_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_KEY, + 'attributes' => ['userId'], + 'lengths' => [Database::LENGTH_KEY], + 'orders' => [Database::ORDER_ASC], + ] + ], + ], + + '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' => [], + ], + [ + '$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' => [ + [ + '$id' => '_key_bucket', + 'type' => Database::INDEX_KEY, + '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], + ] + ], + ], + + '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' => [ + [ + '$id' => '_fulltext_name', + 'type' => Database::INDEX_FULLTEXT, + 'attributes' => ['name'], + 'lengths' => [1024], + 'orders' => [Database::ORDER_ASC], + ] + ], + ], + + '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' => [ + [ + '$id' => '_key_function', + 'type' => Database::INDEX_KEY, + 'attributes' => ['functionId'], + 'lengths' => [Database::LENGTH_KEY], + 'orders' => [Database::ORDER_ASC], + ] + ], + ], + + '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' => [ + [ + '$id' => '_key_function', + 'type' => Database::INDEX_KEY, + 'attributes' => ['functionId'], + 'lengths' => [Database::LENGTH_KEY], + 'orders' => [Database::ORDER_ASC], + ] + ], + ], +]; + +/* + * 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/account.php b/app/controllers/api/account.php index 1801a26e39..5d9e121772 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -1,30 +1,29 @@ 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 */ $email = \strtolower($email); @@ -75,40 +74,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', [], APP_LIMIT_USERS); 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' => ['role:all'], + '$write' => ['user:'.$userId], 'email' => $email, 'emailVerification' => false, 'status' => Auth::USER_STATUS_UNACTIVATED, @@ -117,7 +97,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); } @@ -128,20 +112,14 @@ 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') ->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') @@ -163,29 +141,24 @@ 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 */ $email = \strtolower($email); $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') @@ -205,8 +178,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()]], + '$id' => $dbForInternal->getId(), 'userId' => $profile->getId(), 'provider' => Auth::SESSION_PROVIDER_EMAIL, 'providerUid' => $email, @@ -220,20 +192,10 @@ 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); - } - $audits ->setParam('userId', $profile->getId()) ->setParam('event', 'account.sessions.create') @@ -259,7 +221,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') @@ -385,16 +347,16 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') ->inject('response') ->inject('project') ->inject('user') - ->inject('projectDB') + ->inject('dbForInternal') ->inject('geodb') ->inject('audits') ->inject('events') - ->action(function ($provider, $code, $state, $request, $response, $project, $user, $projectDB, $geodb, $audits, $events) use ($oauthDefaultSuccess) { + ->action(function ($provider, $code, $state, $request, $response, $project, $user, $dbForInternal, $geodb, $audits, $events) 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 */ @@ -458,13 +420,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, @@ -477,25 +445,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', [], APP_LIMIT_COUNT); if($sum >= $limit) { throw new Exception('Project registration is restricted. Contact your administrator for more information.', 501); @@ -505,9 +461,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' => ['role:all'], + '$write' => ['user:'.$userId], 'email' => $email, 'emailVerification' => true, 'status' => Auth::USER_STATUS_ACTIVATED, // Email should already be authenticated by OAuth2 provider @@ -516,16 +474,16 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') 'registration' => \time(), 'reset' => false, 'name' => $name, - ], ['email' => $email]); + 'prefs' => [], + 'sessions' => [], + 'tokens' => [], + 'memberships' => [], + ])); } catch (Duplicate $th) { throw new Exception('Account already exists', 409); } Authorization::reset(); - - if (false === $user) { - throw new Exception('Failed saving user to DB', 500); - } } } @@ -540,8 +498,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']]], + '$id' => $dbForInternal->getId(), 'userId' => $user->getId(), 'provider' => $provider, 'providerUid' => $oauth2ID, @@ -569,11 +526,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') Authorization::setRole('user:'.$user->getId()); - $user = $projectDB->updateDocument($user->getArrayCopy()); - - if (false === $user) { - throw new Exception('Failed saving user to DB', 500); - } + $user = $dbForInternal->updateDocument('users', $user->getId(), $user); $audits ->setParam('userId', $user->getId()) @@ -582,7 +535,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') ->setParam('data', ['provider' => $provider]) ; - $events->setParam('eventData', $response->output($session, Response::MODEL_SESSION)); + $events->setParam('eventData', $response->output2($session, Response::MODEL_SESSION)); if (!Config::getParam('domainVerification')) { $response @@ -631,16 +584,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 */ @@ -650,20 +603,14 @@ 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); } $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', [], APP_LIMIT_COUNT); if($sum >= $limit) { throw new Exception('Project registration is restricted. Contact your administrator for more information.', 501); @@ -671,30 +618,27 @@ App::post('/v1/account/sessions/anonymous') } Authorization::disable(); - try { - $user = $projectDB->createDocument([ - '$collection' => Database::SYSTEM_COLLECTION_USERS, - '$permissions' => [ - 'read' => ['*'], - 'write' => ['user:{self}'] - ], - 'email' => null, - 'emailVerification' => false, - 'status' => Auth::USER_STATUS_UNACTIVATED, - 'password' => null, - 'passwordUpdate' => \time(), - 'registration' => \time(), - 'reset' => false, - 'name' => null - ]); - } 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' => ['role:all'], + '$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 @@ -704,8 +648,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']]], + '$id' => $dbForInternal->getId(), 'userId' => $user->getId(), 'provider' => Auth::SESSION_PROVIDER_ANONYMOUS, 'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak @@ -721,19 +664,8 @@ App::post('/v1/account/sessions/anonymous') Authorization::setRole('user:'.$user->getId()); - $session = $projectDB->createDocument($session->getArrayCopy()); - - if (false === $session) { - throw new Exception('Failed saving session to DB', 500); - } - - $user->setAttribute('sessions', $session, 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->setAttribute('sessions', $session, Document::SET_TYPE_APPEND)); $audits ->setParam('userId', $user->getId()) @@ -758,7 +690,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') @@ -779,7 +711,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(); @@ -798,16 +730,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') @@ -825,9 +756,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') @@ -845,11 +776,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') @@ -868,7 +799,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', []); @@ -886,9 +817,9 @@ App::get('/v1/account/sessions') $sessions[$key] = $session; } - $response->dynamic(new Document([ + $response->dynamic2(new Document([ + 'sessions' => $sessions, 'sum' => count($sessions), - 'sessions' => $sessions ]), Response::MODEL_SESSION_LIST); }); @@ -904,22 +835,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 Appwrite\Database\Document $user */ + /** @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(), [ @@ -950,7 +878,7 @@ App::get('/v1/account/logs') $output[$i] = new Document(array_merge([ 'event' => $log['event'], 'ip' => $log['ip'], - 'time' => \strtotime($log['time']), + 'time' => $log['time'], ], $detector->getOS(), $detector->getClient(), $detector->getDevice())); $record = $geodb->get($log['ip']); @@ -965,7 +893,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') @@ -983,21 +911,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()) @@ -1005,7 +927,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') @@ -1024,12 +946,12 @@ App::patch('/v1/account/password') ->param('oldPassword', '', new Password(), 'Old user password. Must be between 6 to 32 chars.', true) ->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 */ // Check old password only if its an existing user. @@ -1037,14 +959,10 @@ App::patch('/v1/account/password') throw new Exception('Invalid credentials', 401); } - $user = $projectDB->updateDocument(\array_merge($user->getArrayCopy(), [ - 'password' => Auth::passwordHash($password), - 'passwordUpdate' => \time(), - ])); - - if (false === $user) { - throw new Exception('Failed saving user to DB', 500); - } + $user = $dbForInternal->updateDocument('users', $user->getId(), $user + ->setAttribute('password', Auth::passwordHash($password)) + ->setAttribute('passwordUpdate', \time()) + ); $audits ->setParam('userId', $user->getId()) @@ -1052,7 +970,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') @@ -1071,12 +989,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 @@ -1089,40 +1007,25 @@ App::patch('/v1/account/email') } $email = \strtolower($email); - $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, [\strtolower($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 = $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 + ); - $user = $projectDB->updateDocument(\array_merge( - $user->getArrayCopy(), - ($isAnonymousUser ? [ 'password' => Auth::passwordHash($password) ] : []), - [ - 'email' => $email, - 'emailVerification' => false, - ] - )); - - if (false === $user) { - throw new Exception('Failed saving user to DB', 500); - } - $audits ->setParam('userId', $user->getId()) ->setParam('event', 'account.update.email') ->setParam('resource', 'users/'.$user->getId()) ; - $response->dynamic($user, Response::MODEL_USER); + $response->dynamic2($user, Response::MODEL_USER); }); App::patch('/v1/account/prefs') @@ -1140,28 +1043,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') @@ -1178,25 +1075,19 @@ 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, - ])); - - if (false === $user) { - throw new Exception('Failed saving user to DB', 500); - } + $user = $dbForInternal->updateDocument('users', $user->getId(), $user->setAttribute('status', Auth::USER_STATUS_BLOCKED)); //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 @@ -1214,7 +1105,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')) { @@ -1246,14 +1137,16 @@ App::delete('/v1/account/sessions/:sessionId') ->inject('request') ->inject('response') ->inject('user') - ->inject('projectDB') + ->inject('dbForInternal') + ->inject('locale') ->inject('audits') ->inject('events') - ->action(function ($sessionId, $request, $response, $user, $projectDB, $audits, $events) { + ->action(function ($sessionId, $request, $response, $user, $dbForInternal, $locale, $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 Utopia\Locale\Locale $locale */ /** @var Appwrite\Event\Event $audits */ /** @var Appwrite\Event\Event $events */ @@ -1264,13 +1157,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()) @@ -1281,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 @@ -1295,8 +1189,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(); @@ -1321,14 +1217,16 @@ App::delete('/v1/account/sessions') ->inject('request') ->inject('response') ->inject('user') - ->inject('projectDB') + ->inject('dbForInternal') + ->inject('locale') ->inject('audits') ->inject('events') - ->action(function ($request, $response, $user, $projectDB, $audits, $events) { + ->action(function ($request, $response, $user, $dbForInternal, $locale, $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 Utopia\Locale\Locale $locale */ /** @var Appwrite\Event\Event $audits */ /** @var Appwrite\Event\Event $events */ @@ -1338,10 +1236,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') @@ -1354,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); @@ -1364,11 +1261,13 @@ 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([ + 'sessions' => $sessions, 'sum' => count($sessions), - 'sessions' => $sessions ]), Response::MODEL_SESSION_LIST)) ; @@ -1393,16 +1292,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 */ @@ -1411,17 +1310,11 @@ App::post('/v1/account/recovery') $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::$roles); $isAppUser = Auth::isAppUser(Authorization::$roles); - - $email = \strtolower($email); - $profile = $projectDB->getCollectionFirst([ // Get user by email address - 'limit' => 1, - 'filters' => [ - '$collection='.Database::SYSTEM_COLLECTION_USERS, - 'email='.$email, - ], - ]); - if (empty($profile)) { + $email = \strtolower($email); + $profile = $dbForInternal->findFirst('users', [new Query('email', Query::TYPE_EQUAL, [$email])], 1); // Get user by email address + + if (!$profile) { throw new Exception('User not found', 404); // TODO maybe hide this } @@ -1431,8 +1324,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()]], + '$id' => $dbForInternal->getId(), 'userId' => $profile->getId(), 'type' => Auth::TOKEN_TYPE_RECOVERY, 'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak @@ -1443,19 +1335,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]); @@ -1492,7 +1374,7 @@ App::post('/v1/account/recovery') $events ->setParam('eventData', - $response->output($recovery->setAttribute('secret', $secret), + $response->output2($recovery->setAttribute('secret', $secret), Response::MODEL_TOKEN )) ; @@ -1507,10 +1389,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') @@ -1532,30 +1412,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); @@ -1563,33 +1438,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') @@ -1611,17 +1486,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 */ @@ -1633,8 +1508,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()]], + '$id' => $dbForInternal->getId(), 'userId' => $user->getId(), 'type' => Auth::TOKEN_TYPE_VERIFICATION, 'secret' => Auth::hash($verificationSecret), // One way hash encryption to protect DB leak @@ -1645,19 +1519,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]); @@ -1694,7 +1558,7 @@ App::post('/v1/account/verification') $events ->setParam('eventData', - $response->output($verification->setAttribute('secret', $verificationSecret), + $response->output2($verification->setAttribute('secret', $verificationSecret), Response::MODEL_TOKEN )) ; @@ -1709,10 +1573,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') @@ -1733,27 +1595,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); @@ -1761,29 +1618,26 @@ App::put('/v1/account/verification') Authorization::setRole('user:'.$profile->getId()); - $profile = $projectDB->updateDocument(\array_merge($profile->getArrayCopy(), [ - 'emailVerification' => true, - ])); - - if (false === $profile) { - throw new Exception('Failed saving user to DB', 500); - } + $profile = $dbForInternal->updateDocument('users', $profile->getId(), $profile->setAttribute('emailVerification', true)); /** * 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); - }); \ No newline at end of file + $response->dynamic2($verification, Response::MODEL_TOKEN); + }); 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 0f009aa404..78c2391dd4 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,11 @@ use Utopia\Storage\Validator\Upload; 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; use Utopia\Validator\ArrayList; use Utopia\Validator\Assoc; use Utopia\Validator\Text; @@ -21,7 +23,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 +46,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,19 +62,13 @@ 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); - } - - $response - ->setStatusCode(Response::STATUS_CODE_CREATED) - ->dynamic($function, Response::MODEL_FUNCTION) - ; + $response->setStatusCode(Response::STATUS_CODE_CREATED); + $response->dynamic2($function, Response::MODEL_FUNCTION); }); App::get('/v1/functions') @@ -95,24 +87,16 @@ 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, - ], - ]); + $queries = ($search) ? [new Query('name', Query::TYPE_SEARCH, [$search])] : []; - $response->dynamic(new Document([ - 'sum' => $projectDB->getSum(), - 'functions' => $results + $response->dynamic2(new Document([ + 'functions' => $dbForInternal->find('functions', $queries, $limit, $offset, ['_id'], [$orderType]), + 'sum' => $dbForInternal->count('functions', $queries, APP_LIMIT_COUNT), ]), Response::MODEL_FUNCTION_LIST); }); @@ -129,18 +113,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 +138,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,39 +268,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); - } + ]))); if ($next && $schedule !== $original) { ResqueScheduler::enqueueAt($next, 'v1-functions', 'FunctionsV1', [ @@ -328,7 +306,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') @@ -346,32 +324,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', [ @@ -383,11 +361,7 @@ App::patch('/v1/functions/:functionId/tag') ]); // Async task rescheduale } - if (false === $function) { - 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') @@ -403,26 +377,26 @@ 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); } $deletes ->setParam('type', DELETE_TYPE_DOCUMENT) - ->setParam('document', $function->getArrayCopy()) + ->setParam('document', $function) ; $response->noContent(); @@ -447,17 +421,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); } @@ -496,31 +470,22 @@ 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); - } + ])); $usage ->setParam('storage', $tag->getAttribute('size', 0)) ; - $response - ->setStatusCode(Response::STATUS_CODE_CREATED) - ->dynamic($tag, Response::MODEL_TAG) - ; + $response->setStatusCode(Response::STATUS_CODE_CREATED); + $response->dynamic2($tag, Response::MODEL_TAG); }); App::get('/v1/functions/:functionId/tags') @@ -540,31 +505,25 @@ 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(), - ], - ]); - $response->dynamic(new Document([ - 'sum' => $projectDB->getSum(), - 'tags' => $results + $queries[] = new Query('functionId', Query::TYPE_EQUAL, [$function->getId()]); + + $results = $dbForInternal->find('tags', $queries, $limit, $offset, ['_id'], [$orderType]); + $sum = $dbForInternal->count('tags', $queries, APP_LIMIT_COUNT); + + $response->dynamic2(new Document([ + 'tags' => $results, + 'sum' => $sum, ]), Response::MODEL_TAG_LIST); }); @@ -582,28 +541,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') @@ -620,45 +579,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); - } + ]))); } $usage @@ -687,29 +642,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); } @@ -717,33 +672,27 @@ 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(); - - 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 @@ -777,10 +726,8 @@ App::post('/v1/functions/:functionId/executions') 'jwt' => $jwt, ]); - $response - ->setStatusCode(Response::STATUS_CODE_CREATED) - ->dynamic($execution, Response::MODEL_EXECUTION) - ; + $response->setStatusCode(Response::STATUS_CODE_CREATED); + $response->dynamic2($execution, Response::MODEL_EXECUTION); }); App::get('/v1/functions/:functionId/executions') @@ -795,38 +742,33 @@ 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('projectDB') - ->action(function ($functionId, $search, $limit, $offset, $orderType, $response, $projectDB) { + ->inject('dbForInternal') + ->action(function ($functionId, $limit, $offset, $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(), - ], - ]); - $response->dynamic(new Document([ - 'sum' => $projectDB->getSum(), - 'executions' => $results + $results = $dbForInternal->find('executions', [ + new Query('functionId', Query::TYPE_EQUAL, [$function->getId()]), + ], $limit, $offset, ['_id'], [Database::ORDER_DESC]); + + $sum = $dbForInternal->count('executions', [ + new Query('functionId', Query::TYPE_EQUAL, [$function->getId()]), + ], APP_LIMIT_COUNT); + + $response->dynamic2(new Document([ + 'executions' => $results, + 'sum' => $sum, ]), Response::MODEL_EXECUTION_LIST); }); @@ -844,28 +786,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/controllers/api/health.php b/app/controllers/api/health.php index dbbd59f8c3..991ebd552a 100644 --- a/app/controllers/api/health.php +++ b/app/controllers/api/health.php @@ -267,7 +267,7 @@ App::get('/v1/health/anti-virus') 'status' => (@$antiVirus->ping()) ? 'online' : 'offline', 'version' => @$antiVirus->version(), ]); - } catch( \Exception $e) { + } catch (Throwable $e) { $response->json([ 'status' => 'offline', 'version' => '', diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index 81c4fdab03..bdcb89bb3d 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -1,25 +1,27 @@ desc('Create Project') @@ -43,55 +45,95 @@ 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('dbForConsole') + ->inject('dbForInternal') + ->inject('dbForExternal') ->inject('consoleDB') - ->inject('projectDB') - ->action(function ($name, $teamId, $description, $logo, $url, $legalName, $legalCountry, $legalState, $legalCity, $legalAddress, $legalTaxId, $response, $consoleDB, $projectDB) { + ->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 */ - /** @var Appwrite\Database\Database $projectDB */ - $team = $projectDB->getDocument($teamId); + $team = $dbForConsole->getDocument('teams', $teamId); - if (empty($team->getId()) || Database::SYSTEM_COLLECTION_TEAMS != $team->getCollection()) { + if ($team->isEmpty()) { 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' => [], + 'usersAuthEmailPassword' => true, + 'usersAuthAnonymous' => true, + 'usersAuthInvites' => true, + 'usersAuthJWT' => true, + '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'); + $dbForInternal->create(); + $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); + + 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) { + $dbForInternal->createIndex( + $key, + $index['$id'], + $index['type'], + $index['attributes'], + $index['lengths'], + $index['orders'], + ); + } } $consoleDB->createNamespace($project->getId()); - $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') @@ -109,24 +151,19 @@ 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, - ], - ]); + $queries = ($search) ? [new Query('name', Query::TYPE_SEARCH, [$search])] : []; - $response->dynamic(new Document([ - 'sum' => $consoleDB->getSum(), - 'projects' => $results + $results = $dbForConsole->find('projects', $queries, $limit, $offset, ['_id'], [$orderType]); + $sum = $dbForConsole->count('projects', $queries, APP_LIMIT_COUNT); + + $response->dynamic2(new Document([ + 'projects' => $results, + 'sum' => $sum, ]), Response::MODEL_PROJECT_LIST); }); @@ -142,18 +179,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') @@ -166,22 +203,22 @@ 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); } - if(App::getEnv('_APP_USAGE_STATS', 'enabled') == 'enabled') { + if (App::getEnv('_APP_USAGE_STATS', 'enabled') == 'enabled') { $period = [ '24h' => [ @@ -205,44 +242,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, @@ -256,14 +293,13 @@ App::get('/v1/projects/:projectId/usage') $functions = []; } - // Users $projectDB->getCollection([ 'limit' => 0, 'offset' => 0, 'filters' => [ - '$collection='.Database::SYSTEM_COLLECTION_USERS, + '$collection=users', ], ]); @@ -275,7 +311,7 @@ App::get('/v1/projects/:projectId/usage') 'limit' => 100, 'offset' => 0, 'filters' => [ - '$collection='.Database::SYSTEM_COLLECTION_COLLECTIONS, + '$collection=collections', ], ]); @@ -288,7 +324,7 @@ App::get('/v1/projects/:projectId/usage') 'limit' => 0, 'offset' => 0, 'filters' => [ - '$collection='.$collection['$id'], + '$collection=' . $collection['$id'], ], ]); @@ -341,15 +377,15 @@ App::get('/v1/projects/:projectId/usage') [ 'attribute' => 'sizeOriginal', 'filters' => [ - '$collection='.Database::SYSTEM_COLLECTION_FILES, + '$collection=files', ], ] - ) + + ) + $projectDB->getCount( [ 'attribute' => 'size', 'filters' => [ - '$collection='.Database::SYSTEM_COLLECTION_TAGS, + '$collection=tags', ], ] ), @@ -379,35 +415,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') @@ -425,27 +457,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') @@ -461,26 +489,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') @@ -494,32 +518,28 @@ 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('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') @@ -535,51 +555,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); } @@ -606,25 +609,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, @@ -633,22 +632,12 @@ App::post('/v1/projects/:projectId/webhooks') 'httpPass' => $httpPass, ]); - if (false === $webhook) { - throw new Exception('Failed saving webhook to DB', 500); - } + $project = $dbForConsole->updateDocument('projects', $project->getId(), $project + ->setAttribute('webhooks', $webhook, Document::SET_TYPE_APPEND) + ); - $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); - } - - $response - ->setStatusCode(Response::STATUS_CODE_CREATED) - ->dynamic($webhook, Response::MODEL_WEBHOOK) - ; + $response->setStatusCode(Response::STATUS_CODE_CREATED); + $response->dynamic2($webhook, Response::MODEL_WEBHOOK); }); App::get('/v1/projects/:projectId/webhooks') @@ -663,22 +652,22 @@ 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([ + 'webhooks' => $webhooks, 'sum' => count($webhooks), - 'webhooks' => $webhooks ]), Response::MODEL_WEBHOOK_LIST); }); @@ -695,24 +684,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') @@ -734,39 +723,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 - ->setAttribute('name', $name) - ->setAttribute('events', $events) - ->setAttribute('url', $url) - ->setAttribute('security', $security) - ->setAttribute('httpUser', $httpUser) - ->setAttribute('httpPass', $httpPass) - ; + $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); - } + $dbForConsole->updateDocument('projects', $project->getId(), $project); - $response->dynamic($webhook, Response::MODEL_WEBHOOK); + $response->dynamic2($webhook, Response::MODEL_WEBHOOK); }); App::delete('/v1/projects/:projectId/webhooks/:webhookId') @@ -781,26 +768,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(); }); @@ -821,44 +804,30 @@ 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 = $dbForConsole->updateDocument('projects', $project->getId(), $project + ->setAttribute('keys', $key, Document::SET_TYPE_APPEND) + ); - $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); - } - - $response - ->setStatusCode(Response::STATUS_CODE_CREATED) - ->dynamic($key, Response::MODEL_KEY) - ; + $response->setStatusCode(Response::STATUS_CODE_CREATED); + $response->dynamic2($key, Response::MODEL_KEY); }); App::get('/v1/projects/:projectId/keys') @@ -873,22 +842,22 @@ 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 */ - - $project = $consoleDB->getDocument($projectId); + /** @var Utopia\Database\Database $dbForConsole */ - if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { + $project = $dbForConsole->getDocument('projects', $projectId); + + if ($project->isEmpty()) { throw new Exception('Project not found', 404); } $keys = $project->getAttribute('keys', []); - $response->dynamic(new Document([ + $response->dynamic2(new Document([ + 'keys' => $keys, 'sum' => count($keys), - 'keys' => $keys ]), Response::MODEL_KEY_LIST); }); @@ -905,21 +874,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') @@ -937,33 +906,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 - ->setAttribute('name', $name) - ->setAttribute('scopes', $scopes) - ; + $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); - } + $dbForConsole->updateDocument('projects', $project->getId(), $project); - $response->dynamic($key, Response::MODEL_KEY); + $response->dynamic2($key, Response::MODEL_KEY); }); App::delete('/v1/projects/:projectId/keys/:keyId') @@ -978,26 +945,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(); }); @@ -1025,28 +988,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, @@ -1063,26 +1021,16 @@ 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()); } - $response - ->setStatusCode(Response::STATUS_CODE_CREATED) - ->dynamic($task, Response::MODEL_TASK) - ; + $response->setStatusCode(Response::STATUS_CODE_CREATED); + $response->dynamic2($task, Response::MODEL_TASK); }); App::get('/v1/projects/:projectId/tasks') @@ -1097,22 +1045,22 @@ 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([ + 'tasks' => $tasks, 'sum' => count($tasks), - 'tasks' => $tasks ]), Response::MODEL_TASK_LIST); }); @@ -1130,24 +1078,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') @@ -1172,18 +1120,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); @@ -1191,32 +1139,29 @@ 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 - ->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) - ; + $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'); - 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') @@ -1231,26 +1176,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(); }); @@ -1274,23 +1215,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, @@ -1300,24 +1237,14 @@ App::post('/v1/projects/:projectId/platforms') 'dateUpdated' => \time(), ]); - if (false === $platform) { - throw new Exception('Failed saving platform to DB', 500); - } + $project = $dbForConsole->updateDocument('projects', $project->getId(), $project + ->setAttribute('platforms', $platform, Document::SET_TYPE_APPEND) + ); - $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); - } - - $response - ->setStatusCode(Response::STATUS_CODE_CREATED) - ->dynamic($platform, Response::MODEL_PLATFORM) - ; + $response->setStatusCode(Response::STATUS_CODE_CREATED); + $response->dynamic2($platform, Response::MODEL_PLATFORM); }); - + App::get('/v1/projects/:projectId/platforms') ->desc('List Platforms') ->groups(['api', 'projects']) @@ -1330,22 +1257,22 @@ 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([ + 'platforms' => $platforms, 'sum' => count($platforms), - 'platforms' => $platforms ]), Response::MODEL_PLATFORM_LIST); }); @@ -1362,24 +1289,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') @@ -1399,18 +1326,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); @@ -1424,11 +1351,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') @@ -1443,26 +1376,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(); }); @@ -1482,37 +1411,33 @@ 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); } $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); - $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(), @@ -1521,22 +1446,12 @@ App::post('/v1/projects/:projectId/domains') 'certificateId' => null, ]); - if (false === $domain) { - throw new Exception('Failed saving domain to DB', 500); - } + $project = $dbForConsole->updateDocument('projects', $project->getId(), $project + ->setAttribute('domains', $domain, Document::SET_TYPE_APPEND) + ); - $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); - } - - $response - ->setStatusCode(Response::STATUS_CODE_CREATED) - ->dynamic($domain, Response::MODEL_DOMAIN) - ; + $response->setStatusCode(Response::STATUS_CODE_CREATED); + $response->dynamic2($domain, Response::MODEL_DOMAIN); }); App::get('/v1/projects/:projectId/domains') @@ -1551,22 +1466,22 @@ 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([ + 'domains' => $domains, 'sum' => count($domains), - 'domains' => $domains ]), Response::MODEL_DOMAIN_LIST); }); @@ -1583,24 +1498,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') @@ -1616,18 +1531,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); @@ -1636,27 +1551,24 @@ 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) { - 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 - ->setAttribute('verification', true) - ; + $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', [ @@ -1664,7 +1576,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') @@ -1679,32 +1591,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/api/storage.php b/app/controllers/api/storage.php index c4fe8c8494..a093f86cad 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; @@ -22,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') @@ -43,14 +43,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 */ @@ -122,14 +122,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), @@ -137,17 +134,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), - ]); - - if (false === $file) { - throw new Exception('Failed saving file to DB', 500); - } + 'openSSLVersion' => '1', + 'openSSLCipher' => OpenSSL::CIPHER_AES_128_GCM, + 'openSSLTag' => \bin2hex($tag), + 'openSSLIV' => \bin2hex($iv), + ])); $audits ->setParam('event', 'storage.files.create') @@ -158,9 +150,8 @@ App::post('/v1/storage/files') ->setParam('storage', $sizeActual) ; - $response - ->setStatusCode(Response::STATUS_CODE_CREATED) - ->dynamic($file, Response::MODEL_FILE) + $response->setStatusCode(Response::STATUS_CODE_CREATED); + $response->dynamic2($file, Response::MODEL_FILE); ; }); @@ -180,24 +171,16 @@ 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, - ], - ]); + $queries = ($search) ? [new Query('name', Query::TYPE_SEARCH, $search)] : []; - $response->dynamic(new Document([ - 'sum' => $projectDB->getSum(), - 'files' => $results + $response->dynamic2(new Document([ + 'files' => $dbForInternal->find('files', $queries, $limit, $offset, ['_id'], [$orderType]), + 'sum' => $dbForInternal->count('files', $queries, APP_LIMIT_COUNT), ]), Response::MODEL_FILE_LIST); }); @@ -214,18 +197,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') @@ -254,12 +237,12 @@ App::get('/v1/storage/files/:fileId/preview') ->inject('request') ->inject('response') ->inject('project') - ->inject('projectDB') - ->action(function ($fileId, $width, $height, $gravity, $quality, $borderWidth, $borderColor, $borderRadius, $opacity, $rotation, $background, $output, $request, $response, $project, $projectDB) { + ->inject('dbForInternal') + ->action(function ($fileId, $width, $height, $gravity, $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'; @@ -282,16 +265,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)) { @@ -329,11 +312,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')) ); } @@ -395,14 +378,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); } @@ -417,14 +400,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')) ); } @@ -453,15 +436,15 @@ App::get('/v1/storage/files/:fileId/view') ->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); $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); } @@ -482,14 +465,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')) ); } @@ -524,37 +507,31 @@ 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' => '', - ])); - - if (false === $file) { - throw new Exception('Failed saving file to DB', 500); - } + $file = $dbForInternal->updateDocument('files', $fileId, new Document(\array_merge($file->getArrayCopy(), [ + '$read' => $read, + '$write' => $write, + 'bucketId' => '', + ]))); $audits ->setParam('event', 'storage.files.update') ->setParam('resource', 'storage/files/'.$file->getId()) ; - $response->dynamic($file, Response::MODEL_FILE); + $response->dynamic2($file, Response::MODEL_FILE); }); App::delete('/v1/storage/files/:fileId') @@ -570,27 +547,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); } } @@ -605,59 +582,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', 'root') -// ->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/api/teams.php b/app/controllers/api/teams.php index 3b3039a0fd..f8c3d25822 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,15 @@ 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,24 +99,19 @@ 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, - ], - ]); + $queries = ($search) ? [new Query('name', Query::TYPE_SEARCH, [$search])] : []; - $response->dynamic(new Document([ - 'sum' => $projectDB->getSum(), - 'teams' => $results + $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, ]), Response::MODEL_TEAM_LIST); }); @@ -146,18 +128,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 +157,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 +186,36 @@ 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 - foreach ($memberships as $member) { - if (!$projectDB->deleteDocument($member->getId())) { + // TODO delete all members individually from the user object + foreach ($memberships as $membership) { + if (!$dbForInternal->deleteDocument('memberships', $membership->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 */ @@ -288,41 +260,20 @@ App::post('/v1/teams/:teamId/memberships') $email = \strtolower($email); $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' => 2000, - '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, - ], - ]); + $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', [], APP_LIMIT_USERS); if($sum >= $limit) { throw new Exception('Project registration is restricted. Contact your administrator for more information.', 501); @@ -332,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, 'role:all'], + '$write' => ['user:'.$userId], 'email' => $email, 'emailVerification' => false, 'status' => Auth::USER_STATUS_UNACTIVATED, @@ -351,31 +301,19 @@ App::post('/v1/teams/:teamId/memberships') 'registration' => \time(), 'reset' => false, 'name' => $name, + 'prefs' => [], 'sessions' => [], 'tokens' => [], - ], ['email' => $email]); + 'memberships' => [], + ])); } 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; - - 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); @@ -384,11 +322,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' => ['role:all'], + '$write' => ['user:'.$invitee->getId(), 'team:'.$team->getId().'/owner'], 'userId' => $invitee->getId(), 'teamId' => $team->getId(), 'roles' => $roles, @@ -400,28 +336,26 @@ App::post('/v1/teams/:teamId/memberships') if ($isPrivilegedUser || $isAppUser) { // Allow admin to create membership Authorization::disable(); - $membership = $projectDB->createDocument($membership->getArrayCopy()); + 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 = $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); + 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); @@ -467,13 +401,59 @@ 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') + ->desc('Get Team Memberships') + ->groups(['api', 'teams']) + ->label('scope', 'teams.read') + ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT]) + ->label('sdk.namespace', 'teams') + ->label('sdk.method', 'getMemberships') + ->label('sdk.description', '/docs/references/teams/get-team-members.md') + ->label('sdk.response.code', Response::STATUS_CODE_OK) + ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) + ->label('sdk.response.model', Response::MODEL_MEMBERSHIP_LIST) + ->param('teamId', '', new UID(), 'Team unique ID.') + ->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true) + ->param('limit', 25, new Range(0, 100), 'Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true) + ->param('offset', 0, new Range(0, 2000), 'Results offset. The default value is 0. Use this param to manage pagination.', true) + ->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true) + ->inject('response') + ->inject('dbForInternal') + ->action(function ($teamId, $search, $limit, $offset, $orderType, $response, $dbForInternal) { + /** @var Appwrite\Utopia\Response $response */ + /** @var Utopia\Database\Database $dbForInternal */ + + $team = $dbForInternal->getDocument('teams', $teamId); + + if (empty($team->getId())) { + throw new Exception('Team not found', 404); + } + + $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 = []; + + foreach ($memberships as $membership) { + if (empty($membership->getAttribute('userId', null))) { + continue; + } + + $temp = $dbForInternal->getDocument('users', $membership->getAttribute('userId', null))->getArrayCopy(['email', 'name']); + + $users[] = new Document(\array_merge($temp, $membership->getArrayCopy())); + } + + $response->dynamic2(new Document([ + 'memberships' => $users, + 'sum' => $sum, + ]), Response::MODEL_MEMBERSHIP_LIST); }); App::patch('/v1/teams/:teamId/memberships/:membershipId') @@ -494,25 +474,29 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId') ->inject('request') ->inject('response') ->inject('user') - ->inject('projectDB') + ->inject('dbForInternal') ->inject('audits') - ->action(function ($teamId, $membershipId, $roles, $request, $response, $user, $projectDB,$audits) { + ->action(function ($teamId, $membershipId, $roles, $request, $response, $user, $dbForInternal, $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 Appwrite\Event\Event $audits */ - $team = $projectDB->getDocument($teamId); - if (empty($team->getId()) || Database::SYSTEM_COLLECTION_TEAMS != $team->getCollection()) { + $team = $dbForInternal->getDocument('teams', $teamId); + if ($team->isEmpty()) { throw new Exception('Team not found', 404); } - $membership = $projectDB->getDocument($membershipId); - if (empty($membership->getId()) || Database::SYSTEM_COLLECTION_MEMBERSHIPS != $membership->getCollection()) { + $membership = $dbForInternal->getDocument('memberships', $membershipId); + if ($membership->isEmpty()) { throw new Exception('Membership not found', 404); } + $profile = $dbForInternal->getDocument('users', $membership->getAttribute('userId')); + if ($profile->isEmpty()) { + throw new Exception('User not found', 404); + } $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::$roles); $isAppUser = Auth::isAppUser(Authorization::$roles); @@ -524,11 +508,9 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId') // Update the roles $membership->setAttribute('roles', $roles); - $membership = $projectDB->updateDocument($membership->getArrayCopy()); + $membership = $dbForInternal->updateDocument('memberships', $membership->getId(), $membership); - if (false === $membership) { - throw new Exception('Failed updating membership', 500); - } + //TODO sync updated membership in the user $profile object using TYPE_REPLACE $audits ->setParam('userId', $user->getId()) @@ -536,60 +518,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId') ->setParam('resource', 'teams/'.$teamId) ; - $response->dynamic(new Document($membership->getArrayCopy()), Response::MODEL_MEMBERSHIP); - }); - -App::get('/v1/teams/:teamId/memberships') - ->desc('Get Team Memberships') - ->groups(['api', 'teams']) - ->label('scope', 'teams.read') - ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT]) - ->label('sdk.namespace', 'teams') - ->label('sdk.method', 'getMemberships') - ->label('sdk.description', '/docs/references/teams/get-team-members.md') - ->label('sdk.response.code', Response::STATUS_CODE_OK) - ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) - ->label('sdk.response.model', Response::MODEL_MEMBERSHIP_LIST) - ->param('teamId', '', new UID(), 'Team unique ID.') - ->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true) - ->param('limit', 25, new Range(0, 100), 'Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true) - ->param('offset', 0, new Range(0, 2000), 'Results offset. The default value is 0. Use this param to manage pagination.', true) - ->param('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) { - /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $projectDB */ - - $team = $projectDB->getDocument($teamId); - - if (empty($team->getId()) || Database::SYSTEM_COLLECTION_TEAMS != $team->getCollection()) { - 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, - ], - ]); - $users = []; - - foreach ($memberships as $membership) { - if (empty($membership->getAttribute('userId', null))) { - continue; - } - - $temp = $projectDB->getDocument($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($membership, Response::MODEL_MEMBERSHIP); }); App::patch('/v1/teams/:teamId/memberships/:membershipId/status') @@ -611,22 +540,23 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status') ->inject('request') ->inject('response') ->inject('user') - ->inject('projectDB') + ->inject('dbForInternal') ->inject('geodb') ->inject('audits') - ->action(function ($teamId, $membershipId, $userId, $secret, $request, $response, $user, $projectDB, $geodb, $audits) { + ->action(function ($teamId, $membershipId, $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($membershipId); - if (empty($membership->getId()) || Database::SYSTEM_COLLECTION_MEMBERSHIPS != $membership->getCollection()) { - throw new Exception('Invite not found', 404); + $membership = $dbForInternal->getDocument('memberships', $membershipId); + + if (empty($membership->getId())) { + throw new Exception('Membership not found', 404); } if ($membership->getAttribute('teamId') !== $teamId) { @@ -635,11 +565,11 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/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); } @@ -652,13 +582,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/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()) { @@ -682,8 +606,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/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'), @@ -698,24 +621,15 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/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); + $membership = $dbForInternal->updateDocument('memberships', $membership->getId(), $membership); 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.memberships.update.status') @@ -733,10 +647,10 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/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/:membershipId') @@ -753,18 +667,18 @@ App::delete('/v1/teams/:teamId/memberships/:membershipId') ->param('teamId', '', new UID(), 'Team unique ID.') ->param('membershipId', '', new UID(), 'Membership ID.') ->inject('response') - ->inject('projectDB') + ->inject('dbForInternal') ->inject('audits') ->inject('events') - ->action(function ($teamId, $membershipId, $response, $projectDB, $audits, $events) { + ->action(function ($teamId, $membershipId, $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($membershipId); + $membership = $dbForInternal->getDocument('memberships', $membershipId); - if (empty($membership->getId()) || Database::SYSTEM_COLLECTION_MEMBERSHIPS != $membership->getCollection()) { + if (empty($membership->getId())) { throw new Exception('Invite not found', 404); } @@ -772,24 +686,41 @@ App::delete('/v1/teams/:teamId/memberships/:membershipId') throw new Exception('Team IDs don\'t match', 404); } - $team = $projectDB->getDocument($teamId); + $user = $dbForInternal->getDocument('users', $membership->getAttribute('userId')); - if (empty($team->getId()) || Database::SYSTEM_COLLECTION_TEAMS != $team->getCollection()) { + if ($user->isEmpty()) { + throw new Exception('User not found', 404); + } + + $team = $dbForInternal->getDocument('teams', $teamId); + + 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' => \max($team->getAttribute('sum', 0) - 1, 0), // Ensure that sum >= 0 - ])); + $memberships = $user->getAttribute('memberships', []); + + foreach ($memberships as $key => $child) { + /** @var Document $child */ + + if ($membershipId == $child->getId()) { + unset($memberships[$key]); + break; + } } - if (false === $team) { - throw new Exception('Failed saving team to DB', 500); + 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', \max($team->getAttribute('sum', 0) - 1, 0))); } $audits @@ -799,7 +730,7 @@ App::delete('/v1/teams/:teamId/memberships/:membershipId') ; $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 9dd13e34e8..ab90647658 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 */ + /** @var Utopia\Database\Database $dbForInternal */ $email = \strtolower($email); - $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); - } 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' => ['role:all'], + '$write' => ['user:'.$userId], 'email' => $email, 'emailVerification' => false, 'status' => Auth::USER_STATUS_UNACTIVATED, @@ -69,15 +55,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') @@ -96,24 +84,17 @@ 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, ['_id'], [$orderType]); + $sum = $dbForInternal->count('users', [], APP_LIMIT_COUNT); - $response->dynamic(new Document([ - 'sum' => $projectDB->getSum(), - 'users' => $results + $response->dynamic2(new Document([ + 'users' => $results, + 'sum' => $sum, ]), Response::MODEL_USER_LIST); }); @@ -130,18 +111,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') @@ -157,20 +138,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') @@ -186,16 +167,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); } @@ -213,11 +194,11 @@ App::get('/v1/users/:userId/sessions') $sessions[$key] = $session; } - $response->dynamic(new Document([ + $response->dynamic2(new Document([ + 'sessions' => $sessions, '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') @@ -232,29 +213,24 @@ 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('projectDB') + ->inject('dbForInternal') ->inject('locale') ->inject('geodb') - ->action(function ($userId, $response, $register, $project, $projectDB, $locale, $geodb) { + ->action(function ($userId, $response, $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); } - $adapter = new AuditAdapter($register->get('db')); - $adapter->setNamespace('app_'.$project->getId()); - - $audit = new Audit($adapter); + $audit = new Audit($dbForInternal); $countries = $locale->getText('countries'); @@ -303,7 +279,7 @@ App::get('/v1/users/:userId/logs') $output[$i] = new Document([ 'event' => $log['event'], 'ip' => $log['ip'], - 'time' => \strtotime($log['time']), + 'time' => $log['time'], 'osCode' => $osCode, 'osName' => $osName, @@ -330,7 +306,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') @@ -348,26 +324,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, Validator::TYPE_INTEGER), '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/verification') @@ -385,26 +355,20 @@ 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 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(), [ - 'emailVerification' => $emailVerification, - ])); + $user = $dbForInternal->updateDocument('users', $user->getId(), $user->setAttribute('emailVerification', $emailVerification)); - 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') @@ -422,26 +386,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') @@ -458,32 +416,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); } } @@ -504,31 +464,23 @@ 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->getAttribute('sessions', [])); $events - ->setParam('eventData', $response->output($user, Response::MODEL_USER)) + ->setParam('eventData', $response->output2($user, Response::MODEL_USER)) ; // TODO : Response filter implementation @@ -548,39 +500,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' => ['role:all'], + // ])); $deletes ->setParam('type', DELETE_TYPE_DOCUMENT) @@ -588,7 +530,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/controllers/general.php b/app/controllers/general.php index 5892aee47e..b0658f0a5e 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -10,13 +10,13 @@ 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; +use Utopia\Database\Validator\Authorization as Authorization2; Config::setParam('domainVerification', false); Config::setParam('cookieDomain', 'localhost'); @@ -26,13 +26,12 @@ 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 Appwrite\Database\Document $project */ - /** @var Appwrite\Database\Document $user */ + /** @var Utopia\Database\Document $console */ + /** @var Utopia\Database\Document $project */ + /** @var Utopia\Database\Document $user */ /** @var Utopia\Locale\Locale $locale */ - /** @var bool $mode */ /** @var array $clients */ - + $domain = $request->getHostname(); $domains = Config::getParam('domains', []); if (!array_key_exists($domain, $domains)) { @@ -86,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); } @@ -129,8 +132,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,10 +195,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->search('teamId', $project->getAttribute('teamId', null), $user->getAttribute('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; @@ -218,7 +221,7 @@ 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 @@ -237,21 +240,26 @@ App::init(function ($utopia, $request, $response, $console, $project, $consoleDB $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', [])); @@ -259,7 +267,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); } @@ -380,7 +388,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 1e4f5dede2..14555b502f 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -9,18 +9,19 @@ 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 */ - /** @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 */ /** @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())); @@ -34,10 +35,7 @@ App::init(function ($utopia, $request, $response, $project, $user, $register, $e /* * 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 = 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('')) @@ -67,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); @@ -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/controllers/web/home.php b/app/controllers/web/home.php index f3eeb71c94..f4325f7b44 100644 --- a/app/controllers/web/home.php +++ b/app/controllers/web/home.php @@ -44,12 +44,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') @@ -61,13 +61,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'); @@ -239,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/app/http.php b/app/http.php index 89ff646d70..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; @@ -13,7 +11,11 @@ use Swoole\Http\Response as SwooleResponse; use Utopia\App; use Utopia\CLI\Console; use Utopia\Config\Config; -use Utopia\Domains\Domain; +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'); @@ -51,7 +53,59 @@ $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(); + + $audit = new Audit($dbForConsole); + $audit->setup(); + + $adapter = new TimeLimit("", 0, 1, $dbForConsole); + $adapter->setup(); + + 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) { + $dbForConsole->createIndex( + $key, + $index['$id'], + $index['type'], + $index['attributes'], + $index['lengths'], + $index['orders'], + ); + } + } + + Console::success('[Setup] - Server database init completed...'); + } Console::success('Server started succefully (max payload is '.number_format($payloadSize).' bytes)'); @@ -64,10 +118,6 @@ $http->on('start', function (Server $http) use ($payloadSize) { }); }); -Files::load(__DIR__ . '/../public'); - -include __DIR__ . '/controllers/general.php'; - $http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swooleResponse) { $request = new Request($swooleRequest); $response = new Response($swooleResponse); @@ -89,7 +139,10 @@ $http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swo try { Authorization::cleanRoles(); - Authorization::setRole('*'); + Authorization::setRole('role:all'); + + Authorization2::cleanRoles(); + Authorization2::setRole('role:all'); $app->run($request, $response); } catch (\Throwable $th) { diff --git a/app/init.php b/app/init.php index e628b56da7..f79ef3270e 100644 --- a/app/init.php +++ b/app/init.php @@ -30,6 +30,12 @@ use Utopia\Registry\Registry; use MaxMind\Db\Reader; use PHPMailer\PHPMailer\PHPMailer; 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; const APP_NAME = 'Appwrite'; const APP_DOMAIN = 'appwrite.io'; @@ -39,6 +45,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 = 148; const APP_VERSION_STABLE = '0.8.0'; const APP_STORAGE_UPLOADS = '/storage/uploads'; @@ -80,6 +88,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 @@ -142,6 +151,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 */ @@ -381,15 +411,16 @@ 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); + Authorization2::setDefaultStatus(true); Auth::setCookieName('a_session_'.$project->getId()); @@ -411,37 +442,38 @@ 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); + if ($project->isEmpty()) { + $user = new Document2(['$id' => '', '$collection' => 'users']); + } + else { + $user = $dbForInternal->getDocument('users', Auth::$unique); + } } else { - $user = $consoleDB->getDocument(Auth::$unique); - - $user - ->setAttribute('$id', 'admin-'.$user->getAttribute('$id')) - ; + $user = $dbForConsole->getDocument('users', Auth::$unique); } - 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']); } } $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 { @@ -454,34 +486,81 @@ 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 - $user = new Document(['$id' => '', '$collection' => Database::SYSTEM_COLLECTION_USERS]); + if (empty($user->find('$id', $jwtSessionId, 'sessions'))) { // Match JWT to active token + $user = new Document2(['$id' => '', '$collection' => 'users']); } } return $user; -}, ['mode', 'project', 'console', 'request', 'response', 'projectDB', 'consoleDB']); +}, ['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 Utopia\Database\Database $dbForConsole */ + /** @var Utopia\Database\Document $console */ + + $projectId = $request->getParam('project', + $request->getHeader('x-appwrite-project', 'console')); + + if($projectId === 'console') { + return $console; + } Authorization::disable(); + Authorization2::disable(); - $project = $consoleDB->getDocument($request->getParam('project', - $request->getHeader('x-appwrite-project', ''))); + $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(); @@ -501,6 +580,33 @@ App::setResource('projectDB', function($register, $project) { return $projectDB; }, ['register', 'project']); +App::setResource('dbForInternal', function($register, $project) { + $cache = new Cache(new RedisCache($register->get('cache'))); + + $database = new Database2(new MariaDB($register->get('db')), $cache); + $database->setNamespace('project_'.$project->getId().'_internal'); + + return $database; +}, ['register', 'project']); + +App::setResource('dbForExternal', function($register, $project) { + $cache = new Cache(new RedisCache($register->get('cache'))); + + $database = new Database2(new MariaDB($register->get('db')), $cache); + $database->setNamespace('project_'.$project->getId().'_external'); + + return $database; +}, ['register', 'project']); + +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_internal'); + + return $database; +}, ['register']); + 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/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); 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 2ee5f1f20b..7eba230608 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
@@ -243,7 +243,7 @@ $fileLimitHuman = $this->getParam('fileLimitHuman', 0);
(Max file size allowed: )
- +
Add * for wildcard access
diff --git a/app/workers/audits.php b/app/workers/audits.php index 17327a3bb8..d6c2ecd032 100644 --- a/app/workers/audits.php +++ b/app/workers/audits.php @@ -2,8 +2,11 @@ use Appwrite\Resque\Worker; 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 +34,11 @@ class AuditsV1 extends Worker $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 0568327962..cfca6d5190 100644 --- a/app/workers/deletes.php +++ b/app/workers/deletes.php @@ -1,6 +1,8 @@ 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; @@ -77,7 +81,6 @@ class DeletesV1 extends Worker default: Console::error('No delete operation for type: '.$type); break; - } } @@ -165,12 +168,9 @@ class DeletesV1 extends Worker throw new Exception('Failed to delete audit logs. No timestamp provided'); } - $timeLimit = new TimeLimit("", 0, 1, function () use ($register) { - return $register->get('db'); - }); - - $this->deleteForProjectIds(function($projectId) use ($timeLimit, $timestamp){ - $timeLimit->setNamespace('app_'.$projectId); + + $this->deleteForProjectIds(function($projectId) use ($timestamp){ + $timeLimit = new TimeLimit("", 0, 1, $this->getInternalDB($projectId)); $abuse = new Abuse($timeLimit); $status = $abuse->cleanup($timestamp); @@ -187,9 +187,7 @@ class DeletesV1 extends Worker 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); @@ -373,4 +371,18 @@ class DeletesV1 extends Worker return $projectDB; } -} \ No newline at end of file + + /** + * @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; + } +} diff --git a/app/workers/functions.php b/app/workers/functions.php index cbe8ff4f50..54cece9164 100644 --- a/app/workers/functions.php +++ b/app/workers/functions.php @@ -1,17 +1,18 @@ 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': @@ -168,16 +168,7 @@ class FunctionsV1 extends Worker 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(); @@ -219,10 +210,10 @@ class FunctionsV1 extends Worker // 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.')'); } @@ -240,9 +231,9 @@ class FunctionsV1 extends Worker 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(); @@ -260,10 +251,10 @@ class FunctionsV1 extends Worker 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.')'); } @@ -300,7 +291,7 @@ class FunctionsV1 extends Worker $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()) { @@ -309,12 +300,9 @@ class FunctionsV1 extends Worker 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 @@ -323,7 +311,7 @@ class FunctionsV1 extends Worker 'stdout' => '', 'stderr' => '', 'time' => 0, - ]); + ])); if(false === $execution || ($execution instanceof Document && $execution->isEmpty())) { throw new Exception('Failed to create or read execution'); @@ -465,21 +453,17 @@ class FunctionsV1 extends Worker 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(); - if (false === $function) { - throw new Exception('Failed saving execution to DB', 500); - } - $executionUpdate = new Event('v1-webhooks', 'WebhooksV1'); $executionUpdate diff --git a/composer.json b/composer.json index a226226128..d1acf519c0 100644 --- a/composer.json +++ b/composer.json @@ -39,12 +39,13 @@ "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/cache": "0.2.*", + "utopia-php/audit": "dev-feat-utopia-db-integration", + "utopia-php/cache": "0.4.*", "utopia-php/cli": "0.11.*", "utopia-php/config": "0.2.*", + "utopia-php/database": "0.3.*", "utopia-php/locale": "0.3.*", "utopia-php/registry": "0.4.*", "utopia-php/preloader": "0.2.*", @@ -61,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.11", "swoole/ide-helper": "4.6.6", diff --git a/composer.lock b/composer.lock index 9e7affe29c..20e5ea98ae 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": "399d2426ca92e04b6d6fb84a91c316c3", + "content-hash": "bce8a47d7c5e94cc18bd05b1229ad1db", "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.2", + "source": { + "type": "git", + "url": "https://github.com/composer/package-versions-deprecated.git", + "reference": "c6522afe5540d5fc46675043d3ed5a45a740b27c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/c6522afe5540d5fc46675043d3ed5a45a740b27c", + "reference": "c6522afe5540d5fc46675043d3ed5a45a740b27c", + "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.2" + }, + "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": "2021-05-24T07:46:03+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", @@ -1323,22 +1519,100 @@ "time": "2021-02-19T12:13:01+00:00" }, { - "name": "utopia-php/abuse", - "version": "0.4.1", + "name": "symfony/polyfill-php80", + "version": "v1.23.0", "source": { "type": "git", - "url": "https://github.com/utopia-php/abuse.git", - "reference": "8b7973aae4b02489bd22ffea45b985608f13b6d9" + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "eca0bf41ed421bed1b57c4958bab16aa86b757d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/abuse/zipball/8b7973aae4b02489bd22ffea45b985608f13b6d9", - "reference": "8b7973aae4b02489bd22ffea45b985608f13b6d9", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/eca0bf41ed421bed1b57c4958bab16aa86b757d0", + "reference": "eca0bf41ed421bed1b57c4958bab16aa86b757d0", "shasum": "" }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-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.23.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-02-19T12:13:01+00:00" + }, + { + "name": "utopia-php/abuse", + "version": "dev-feat-utopia-db-integration", + "source": { + "type": "git", + "url": "https://github.com/lohanidamodar/abuse", + "reference": "351dba60714321fabcd5028fdf7325f18f343018" + }, "require": { "ext-pdo": "*", - "php": ">=7.4" + "php": ">=7.4", + "utopia-php/database": "0.3.*" }, "require-dev": { "phpunit/phpunit": "^9.4", @@ -1350,7 +1624,6 @@ "Utopia\\Abuse\\": "src/Abuse" } }, - "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], @@ -1362,17 +1635,13 @@ ], "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.1" - }, - "time": "2021-06-05T14:31:33+00:00" + "time": "2021-06-13T07:41:20+00:00" }, { "name": "utopia-php/analytics", @@ -1431,21 +1700,16 @@ }, { "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" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/utopia-php/audit/zipball/154a850170a58667a15e4b65fbabb6cd0b709dd9", - "reference": "154a850170a58667a15e4b65fbabb6cd0b709dd9", - "shasum": "" + "url": "https://github.com/lohanidamodar/audit", + "reference": "b3ca9fa928fdec8c966596cbd85ee1141c79b6eb" }, "require": { "ext-pdo": "*", - "php": ">=7.1" + "php": ">=7.4", + "utopia-php/database": "0.3.*" }, "require-dev": { "phpunit/phpunit": "^9.3", @@ -1457,7 +1721,6 @@ "Utopia\\Audit\\": "src/Audit" } }, - "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], @@ -1469,35 +1732,32 @@ ], "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" - }, - "time": "2020-12-21T17:28:53+00:00" + "time": "2021-06-13T07:41:15+00:00" }, { "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 +1789,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 +1897,69 @@ }, "time": "2020-10-24T09:49:09+00:00" }, + { + "name": "utopia-php/database", + "version": "0.3.2", + "source": { + "type": "git", + "url": "https://github.com/utopia-php/database.git", + "reference": "8e56a2d399d17b2497c8ee0f8bf429ac2da90c56" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/utopia-php/database/zipball/8e56a2d399d17b2497c8ee0f8bf429ac2da90c56", + "reference": "8e56a2d399d17b2497c8ee0f8bf429ac2da90c56", + "shasum": "" + }, + "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": { + "fakerphp/faker": "^1.14", + "phpunit/phpunit": "^9.4", + "utopia-php/cli": "^0.11.0", + "vimeo/psalm": "4.0.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Utopia\\Database\\": "src/Database" + } + }, + "notification-url": "https://packagist.org/downloads/", + "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" + ], + "support": { + "issues": "https://github.com/utopia-php/database/issues", + "source": "https://github.com/utopia-php/database/tree/0.3.2" + }, + "time": "2021-06-12T18:26:26+00:00" + }, { "name": "utopia-php/domains", "version": "v1.1.0", @@ -2387,79 +2710,6 @@ }, "time": "2021-06-07T11:37:50+00:00" }, - { - "name": "composer/package-versions-deprecated", - "version": "1.11.99.2", - "source": { - "type": "git", - "url": "https://github.com/composer/package-versions-deprecated.git", - "reference": "c6522afe5540d5fc46675043d3ed5a45a740b27c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/c6522afe5540d5fc46675043d3ed5a45a740b27c", - "reference": "c6522afe5540d5fc46675043d3ed5a45a740b27c", - "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.2" - }, - "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": "2021-05-24T07:46:03+00:00" - }, { "name": "composer/semver", "version": "3.2.5", @@ -2713,20 +2963,20 @@ }, { "name": "felixfbecker/advanced-json-rpc", - "version": "v3.2.0", + "version": "v3.2.1", "source": { "type": "git", "url": "https://github.com/felixfbecker/php-advanced-json-rpc.git", - "reference": "06f0b06043c7438959dbdeed8bb3f699a19be22e" + "reference": "b5f37dbff9a8ad360ca341f3240dc1c168b45447" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/felixfbecker/php-advanced-json-rpc/zipball/06f0b06043c7438959dbdeed8bb3f699a19be22e", - "reference": "06f0b06043c7438959dbdeed8bb3f699a19be22e", + "url": "https://api.github.com/repos/felixfbecker/php-advanced-json-rpc/zipball/b5f37dbff9a8ad360ca341f3240dc1c168b45447", + "reference": "b5f37dbff9a8ad360ca341f3240dc1c168b45447", "shasum": "" }, "require": { - "netresearch/jsonmapper": "^1.0 || ^2.0", + "netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0 || ^4.0", "php": "^7.1 || ^8.0", "phpdocumentor/reflection-docblock": "^4.3.4 || ^5.0.0" }, @@ -2752,9 +3002,9 @@ "description": "A more advanced JSONRPC implementation", "support": { "issues": "https://github.com/felixfbecker/php-advanced-json-rpc/issues", - "source": "https://github.com/felixfbecker/php-advanced-json-rpc/tree/v3.2.0" + "source": "https://github.com/felixfbecker/php-advanced-json-rpc/tree/v3.2.1" }, - "time": "2021-01-10T17:48:47+00:00" + "time": "2021-06-11T22:34:44+00:00" }, { "name": "felixfbecker/language-server-protocol", @@ -3003,16 +3253,16 @@ }, { "name": "netresearch/jsonmapper", - "version": "v2.1.0", + "version": "v4.0.0", "source": { "type": "git", "url": "https://github.com/cweiske/jsonmapper.git", - "reference": "e0f1e33a71587aca81be5cffbb9746510e1fe04e" + "reference": "8bbc021a8edb2e4a7ea2f8ad4fa9ec9dce2fcb8d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/e0f1e33a71587aca81be5cffbb9746510e1fe04e", - "reference": "e0f1e33a71587aca81be5cffbb9746510e1fe04e", + "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/8bbc021a8edb2e4a7ea2f8ad4fa9ec9dce2fcb8d", + "reference": "8bbc021a8edb2e4a7ea2f8ad4fa9ec9dce2fcb8d", "shasum": "" }, "require": { @@ -3020,10 +3270,10 @@ "ext-pcre": "*", "ext-reflection": "*", "ext-spl": "*", - "php": ">=5.6" + "php": ">=7.1" }, "require-dev": { - "phpunit/phpunit": "~4.8.35 || ~5.7 || ~6.4 || ~7.0", + "phpunit/phpunit": "~7.5 || ~8.0 || ~9.0", "squizlabs/php_codesniffer": "~3.5" }, "type": "library", @@ -3048,9 +3298,9 @@ "support": { "email": "cweiske@cweiske.de", "issues": "https://github.com/cweiske/jsonmapper/issues", - "source": "https://github.com/cweiske/jsonmapper/tree/master" + "source": "https://github.com/cweiske/jsonmapper/tree/v4.0.0" }, - "time": "2020-04-16T18:48:43+00:00" + "time": "2020-12-01T19:48:11+00:00" }, { "name": "nikic/php-parser", @@ -4472,16 +4722,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": { @@ -4524,7 +4774,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": [ { @@ -4532,7 +4782,7 @@ "type": "github" } ], - "time": "2020-10-26T15:55:19+00:00" + "time": "2021-06-11T13:31:12+00:00" }, { "name": "sebastian/lines-of-code", @@ -5471,89 +5721,6 @@ ], "time": "2021-02-19T12:13:01+00:00" }, - { - "name": "symfony/polyfill-php80", - "version": "v1.23.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "eca0bf41ed421bed1b57c4958bab16aa86b757d0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/eca0bf41ed421bed1b57c4958bab16aa86b757d0", - "reference": "eca0bf41ed421bed1b57c4958bab16aa86b757d0", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-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.23.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-02-19T12:13:01+00:00" - }, { "name": "symfony/service-contracts", "version": "v2.4.0", @@ -6003,7 +6170,10 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": { + "utopia-php/abuse": 20, + "utopia-php/audit": 20 + }, "prefer-stable": false, "prefer-lowest": false, "platform": { @@ -6025,5 +6195,5 @@ "platform-overrides": { "php": "8.0" }, - "plugin-api-version": "2.1.0" + "plugin-api-version": "2.0.0" } diff --git a/docker-compose.yml b/docker-compose.yml index fb8a8355f9..be056b8a64 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -62,7 +62,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 @@ -388,7 +388,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 @@ -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/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/Extend/PDO.php b/src/Appwrite/Extend/PDO.php index 72f9f37ac3..a46da92e0a 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(); + return false; + } + + 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); diff --git a/src/Appwrite/Utopia/Response.php b/src/Appwrite/Utopia/Response.php index e0b96834e3..b158400265 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 Response public function setStatusCode(int $code = 200) @@ -277,6 +278,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 * @@ -329,6 +351,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 * diff --git a/src/Appwrite/Utopia/Response/Model/File.php b/src/Appwrite/Utopia/Response/Model/File.php index 5d0d511539..65ac050781 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' => ['role:all'], + '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/General/HTTPTest.php b/tests/e2e/General/HTTPTest.php index c233f241b3..78f77f4d12 100644 --- a/tests/e2e/General/HTTPTest.php +++ b/tests/e2e/General/HTTPTest.php @@ -124,7 +124,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 +133,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/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']]; 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 395eb8ce4f..508866b6a1 100644 --- a/tests/e2e/Services/Storage/StorageBase.php +++ b/tests/e2e/Services/Storage/StorageBase.php @@ -18,9 +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' => ['*'], - 'folderId' => 'xyz', + 'read' => ['role:all'], + 'write' => ['role:all'], ]); $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'], @@ -187,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']); @@ -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 diff --git a/tests/e2e/Services/Teams/TeamsBaseClient.php b/tests/e2e/Services/Teams/TeamsBaseClient.php index 00ae8c186e..13df0dea97 100644 --- a/tests/e2e/Services/Teams/TeamsBaseClient.php +++ b/tests/e2e/Services/Teams/TeamsBaseClient.php @@ -79,6 +79,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 3b5958eecb..68196e4801 100644 --- a/tests/e2e/Services/Teams/TeamsBaseServer.php +++ b/tests/e2e/Services/Teams/TeamsBaseServer.php @@ -79,6 +79,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'], diff --git a/tests/e2e/Services/Webhooks/WebhooksBase.php b/tests/e2e/Services/Webhooks/WebhooksBase.php index 060e8ad0a7..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', @@ -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']]); @@ -79,8 +79,8 @@ trait WebhooksBase 'lastName' => 'Evans', ], - 'read' => ['*'], - 'write' => ['*'], + 'read' => ['role:all'], + 'write' => ['role:all'], ]); $this->assertEquals($document['headers']['status-code'], 201); @@ -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']; @@ -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); @@ -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; } @@ -164,8 +164,8 @@ trait WebhooksBase 'lastName' => 'Cooper', ], - 'read' => ['*'], - 'write' => ['*'], + 'read' => ['role:all'], + 'write' => ['role:all'], ]); $this->assertEquals($document['headers']['status-code'], 201); @@ -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; } @@ -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', ]); @@ -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']); @@ -253,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); @@ -271,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']); @@ -308,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']); 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/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 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 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');