From 556028114122cc195399f4b7f916c64b6a6c9fb0 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Fri, 16 Apr 2021 12:41:35 +0545 Subject: [PATCH 01/52] enabling android platform --- app/views/console/home/index.phtml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/console/home/index.phtml b/app/views/console/home/index.phtml index 1f3e95f1d9..45b7dc5b79 100644 --- a/app/views/console/home/index.phtml +++ b/app/views/console/home/index.phtml @@ -231,8 +231,8 @@ $usageStatsEnabled = $this->getParam('usageStatsEnabled',true);
  • -
  • - +
  • +
  • From b7470da9d76ae9fc09a0a8d78d9744a4419220b2 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Fri, 16 Apr 2021 13:15:35 +0545 Subject: [PATCH 02/52] adding android platform --- app/views/console/home/index.phtml | 36 ++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/app/views/console/home/index.phtml b/app/views/console/home/index.phtml index 45b7dc5b79..2491a03528 100644 --- a/app/views/console/home/index.phtml +++ b/app/views/console/home/index.phtml @@ -317,6 +317,42 @@ $usageStatsEnabled = $this->getParam('usageStatsEnabled',true); + +
  • -
  • - -
  • +
  • + +
  • From 46c6531f731d8a9e5aa67dfb894b026f2c70c2df Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Thu, 27 May 2021 15:13:40 +0200 Subject: [PATCH 05/52] feat(system): add env to configure telegraf --- app/views/install/compose.phtml | 3 +++ docker-compose.yml | 3 +++ 2 files changed, 6 insertions(+) diff --git a/app/views/install/compose.phtml b/app/views/install/compose.phtml index e35ea60d09..cadd619c1f 100644 --- a/app/views/install/compose.phtml +++ b/app/views/install/compose.phtml @@ -377,6 +377,9 @@ services: restart: unless-stopped networks: - appwrite + environment: + - _APP_INFLUXDB_HOST + - _APP_INFLUXDB_PORT networks: gateway: diff --git a/docker-compose.yml b/docker-compose.yml index c9c278431f..d57d87daa9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -435,6 +435,9 @@ services: container_name: appwrite-telegraf networks: - appwrite + environment: + - _APP_INFLUXDB_HOST + - _APP_INFLUXDB_PORT # Dev Tools Start ------------------------------------------------------------------------------------------ # From 75d3c39763aacc3eec8572a5e80cb69e2d2f203d Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Tue, 1 Jun 2021 16:02:50 +0545 Subject: [PATCH 06/52] android changelog and getting started --- docs/sdks/android/CHANGELOG.md | 1 + docs/sdks/android/GETTING_STARTED.md | 55 ++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 docs/sdks/android/CHANGELOG.md create mode 100644 docs/sdks/android/GETTING_STARTED.md diff --git a/docs/sdks/android/CHANGELOG.md b/docs/sdks/android/CHANGELOG.md new file mode 100644 index 0000000000..fa4d35e687 --- /dev/null +++ b/docs/sdks/android/CHANGELOG.md @@ -0,0 +1 @@ +# Change Log \ No newline at end of file diff --git a/docs/sdks/android/GETTING_STARTED.md b/docs/sdks/android/GETTING_STARTED.md new file mode 100644 index 0000000000..6bd618505b --- /dev/null +++ b/docs/sdks/android/GETTING_STARTED.md @@ -0,0 +1,55 @@ +## Getting Started + +### Init your SDK + +

    Initialize your SDK code with your project ID, which can be found in your project settings page. + +```kotlin +import io.appwrite.AppwriteClient +import io.appwrite.services.AccountService + +val client = AppwriteClient(context) + .setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint + .setProject("5df5acd0d48c2") // Your project ID + .setSelfSigned(true) // Remove in production +``` + +Before starting to send any API calls to your new Appwrite instance, make sure your Android emulators has network access to the Appwrite server hostname or IP address. + +When trying to connect to Appwrite from an emulator or a mobile device, localhost is the hostname for the device or emulator and not your local Appwrite instance. You should replace localhost with your private IP as the Appwrite endpoint's hostname. You can also use a service like [ngrok](https://ngrok.com/) to proxy the Appwrite API. + +### Make Your First Request + +

    Once your SDK object is set, access any of the Appwrite services and choose any request to send. Full documentation for any service method you would like to use can be found in your SDK documentation or in the API References section. + +```kotlin +// Register User +val accountService = AccountService(client) +val user = accountService.create( + "email@example.com", + "password" +) +``` + +### Full Example + +```kotlin +import io.appwrite.AppwriteClient +import io.appwrite.services.AccountService + +val client = AppwriteClient(context) + .setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint + .setProject("5df5acd0d48c2") // Your project ID + .setSelfSigned(true) // Remove in production + +val accountService = AccountService(client) +val user = accountService.create( + "email@example.com", + "password" +) +``` + +### Learn more +You can use followng resources to learn more and get help +- 📜 [Appwrite Docs](https://appwrite.io/docs) +- 💬 [Discord Community](https://appwrite.io/discord) \ No newline at end of file From c2651d56af0cc44ee500aa1ac4a65dad883b52fc Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Tue, 1 Jun 2021 16:11:51 +0545 Subject: [PATCH 07/52] improvements --- docs/sdks/android/GETTING_STARTED.md | 36 +++++++++++++++++++--------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/docs/sdks/android/GETTING_STARTED.md b/docs/sdks/android/GETTING_STARTED.md index 6bd618505b..801dcd290c 100644 --- a/docs/sdks/android/GETTING_STARTED.md +++ b/docs/sdks/android/GETTING_STARTED.md @@ -5,10 +5,10 @@

    Initialize your SDK code with your project ID, which can be found in your project settings page. ```kotlin -import io.appwrite.AppwriteClient -import io.appwrite.services.AccountService +import io.appwrite.Client +import io.appwrite.services.Account -val client = AppwriteClient(context) +val client = Client(context) .setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint .setProject("5df5acd0d48c2") // Your project ID .setSelfSigned(true) // Remove in production @@ -24,8 +24,8 @@ When trying to connect to Appwrite from an emulator or a mobile device, localhos ```kotlin // Register User -val accountService = AccountService(client) -val user = accountService.create( +val account = Account(client) +val user = account.create( "email@example.com", "password" ) @@ -34,22 +34,36 @@ val user = accountService.create( ### Full Example ```kotlin -import io.appwrite.AppwriteClient -import io.appwrite.services.AccountService +import io.appwrite.Client +import io.appwrite.services.Account -val client = AppwriteClient(context) +val client = Client(context) .setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint .setProject("5df5acd0d48c2") // Your project ID .setSelfSigned(true) // Remove in production -val accountService = AccountService(client) -val user = accountService.create( +val account = Account(client) +val user = account.create( "email@example.com", "password" ) ``` +### Error Handling +The Apopwrite Android SDK raises `AppwriteException` object with `message`, `code` and `response` properties. You can handle any errors by catching `AppwriteException` and present the `message` to the user or handle it yourself based on provided error information. Below is an example. + +```kotlin +try { + var response = account.create("email@example.com", "password") + Log.d("Appwrite response", response.body?.string()) +} catch(e : AppwriteException) { + Log.e("AppwriteException",e.message.toString()) +} +``` + ### Learn more You can use followng resources to learn more and get help +- 🚀 [Getting Started Tutorial](https://appwrite.io/docs/getting-started-for-android) - 📜 [Appwrite Docs](https://appwrite.io/docs) -- 💬 [Discord Community](https://appwrite.io/discord) \ No newline at end of file +- 💬 [Discord Community](https://appwrite.io/discord) +- - 🚂 [Appwrite Android Playground](https://github.com/appwrite/playground-for-android) \ No newline at end of file From 7dd342b66465b2745d9d641d3c898c81ecdb1193 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Tue, 1 Jun 2021 16:13:19 +0545 Subject: [PATCH 08/52] correction --- docs/sdks/android/GETTING_STARTED.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sdks/android/GETTING_STARTED.md b/docs/sdks/android/GETTING_STARTED.md index 801dcd290c..8d1992fa46 100644 --- a/docs/sdks/android/GETTING_STARTED.md +++ b/docs/sdks/android/GETTING_STARTED.md @@ -25,7 +25,7 @@ When trying to connect to Appwrite from an emulator or a mobile device, localhos ```kotlin // Register User val account = Account(client) -val user = account.create( +val response = account.create( "email@example.com", "password" ) @@ -43,7 +43,7 @@ val client = Client(context) .setSelfSigned(true) // Remove in production val account = Account(client) -val user = account.create( +val response = account.create( "email@example.com", "password" ) From 104f5e56855c09067083e113c709f081f007943d Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Tue, 1 Jun 2021 16:17:45 +0545 Subject: [PATCH 09/52] add platform and oauth details --- docs/sdks/android/GETTING_STARTED.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/docs/sdks/android/GETTING_STARTED.md b/docs/sdks/android/GETTING_STARTED.md index 8d1992fa46..23223d85de 100644 --- a/docs/sdks/android/GETTING_STARTED.md +++ b/docs/sdks/android/GETTING_STARTED.md @@ -1,5 +1,30 @@ ## Getting Started +### Add your Android Platform +To init your SDK and start interacting with Appwrite services, you need to add a new Flutter platform to your project. To add a new platform, go to your Appwrite console, choose the project you created in the step before, and click the 'Add Platform' button. + +From the options, choose to add a new **Flutter** platform and add your app credentials, ignoring iOS. + +Add your app name and package name, Your package name is generally the applicationId in your app-level build.gradle file. By registering your new app platform, you are allowing your app to communicate with the Appwrite API. + +### OAuth +In order to capture the Appwrite OAuth callback url, the following activity needs to be added to your [AndroidManifest.xml](https://github.com/appwrite/playground-for-flutter/blob/master/android/app/src/main/AndroidManifest.xml). Be sure to relpace the **[PROJECT_ID]** string with your actual Appwrite project ID. You can find your Appwrite project ID in you project settings screen in your Appwrite console. + +```xml + + + + + + + + + + + + +``` + ### Init your SDK

    Initialize your SDK code with your project ID, which can be found in your project settings page. From c71920e56f7982a1dc2625a9fa93fb417b3a618a Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Thu, 3 Jun 2021 14:31:58 +0200 Subject: [PATCH 10/52] update appwrite/telegraf to 1.2.0 --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index d57d87daa9..ec4a74a257 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -431,7 +431,7 @@ services: - appwrite-influxdb:/var/lib/influxdb:rw telegraf: - image: appwrite/telegraf:1.1.0 + image: appwrite/telegraf:1.2.0 container_name: appwrite-telegraf networks: - appwrite From 3c03b5f997511d68c7c39ca4427c4f142095cb64 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Thu, 3 Jun 2021 14:57:17 +0200 Subject: [PATCH 11/52] fix(install): update to new telegraf version --- app/views/install/compose.phtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/install/compose.phtml b/app/views/install/compose.phtml index cadd619c1f..1acc350664 100644 --- a/app/views/install/compose.phtml +++ b/app/views/install/compose.phtml @@ -372,7 +372,7 @@ services: - appwrite-influxdb:/var/lib/influxdb:rw telegraf: - image: appwrite/telegraf:1.0.0 + image: appwrite/telegraf:1.2.0 container_name: appwrite-telegraf restart: unless-stopped networks: From 53e9c84bab8f3ae183030d7ba1dc2392dbb33e11 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Thu, 3 Jun 2021 15:36:11 +0200 Subject: [PATCH 12/52] chore(changelog): add telegraf env changes --- CHANGES.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 0dd92eeff6..0f24324eeb 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,7 +8,10 @@ - Renamed *Devices* to *Sessions* - Add Provider Icon to each Session - Add Anonymous Account Placeholder -- Upgraded telegraf docker image version to v1.1.0 +- Upgraded telegraf docker image version to v1.2.0 +- Added new environment variables to the `telegraf` service: + - _APP_INFLUXDB_HOST + - _APP_INFLUXDB_PORT ## Bugs From 12e01f809237b5ad8b803699d30113387d3bbaea Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 7 Jun 2021 11:02:29 +0545 Subject: [PATCH 13/52] integrate abuse and audit --- app/controllers/api/account.php | 12 +- app/controllers/api/projects.php | 183 +++++++++++++++---------------- app/controllers/api/users.php | 10 +- app/controllers/shared/api.php | 72 ++++++------ app/workers/audits.php | 11 +- app/workers/deletes.php | 31 ++++-- composer.json | 14 ++- composer.lock | 143 +++++++++++++++++------- 8 files changed, 280 insertions(+), 196 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 11f4e86a48..91be0299bd 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -21,7 +21,6 @@ use Utopia\Audit\Audit; use Utopia\Audit\Adapters\MySQL as AuditAdapter; use Utopia\Database\Document; use Utopia\Database\Exception\Duplicate; -use Ahc\Jwt\JWT; use Utopia\Database\Query; use Utopia\Database\Validator\Authorization; use Utopia\Database\Validator\UID; @@ -830,22 +829,19 @@ App::get('/v1/account/logs') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_LOG_LIST) ->inject('response') - ->inject('register') - ->inject('project') ->inject('user') ->inject('locale') ->inject('geodb') - ->action(function ($response, $register, $project, $user, $locale, $geodb) { + ->inject('dbForInternal') + ->action(function ($response, $user, $locale, $geodb, $dbForInternal) { /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Document $project */ /** @var Utopia\Database\Document $user */ /** @var Utopia\Locale\Locale $locale */ /** @var MaxMind\Db\Reader $geodb */ + /** @var Utopia\Database\Database $dbForInternal */ - $adapter = new AuditAdapter($register->get('db')); - $adapter->setNamespace('app_'.$project->getId()); - - $audit = new Audit($adapter); + $audit = new Audit($dbForInternal); $countries = $locale->getText('countries'); $logs = $audit->getLogsByUserAndActions($user->getId(), [ diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index 69e0a93314..6af20880a9 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -1,25 +1,25 @@ desc('Create Project') @@ -60,8 +60,8 @@ App::post('/v1/projects') $project = $dbForConsole->createDocument('projects', new Document([ '$collection' => 'projects', - '$read' => ['team:'.$teamId], - '$write' => ['team:'.$teamId.'/owner', 'team:'.$teamId.'/developer'], + '$read' => ['team:' . $teamId], + '$write' => ['team:' . $teamId . '/owner', 'team:' . $teamId . '/developer'], 'name' => $name, 'description' => $description, 'logo' => $logo, @@ -87,9 +87,9 @@ App::post('/v1/projects') $collections = Config::getParam('collections2', []); /** @var array $collections */ - $dbForInternal->setNamespace('project_'.$project->getId().'_internal'); + $dbForInternal->setNamespace('project_' . $project->getId() . '_internal'); $dbForInternal->create(); - $dbForExternal->setNamespace('project_'.$project->getId().'_external'); + $dbForExternal->setNamespace('project_' . $project->getId() . '_external'); $dbForExternal->create(); foreach ($collections as $key => $collection) { @@ -206,7 +206,7 @@ App::get('/v1/projects/:projectId/usage') throw new Exception('Project not found', 404); } - if(App::getEnv('_APP_USAGE_STATS', 'enabled') == 'enabled') { + if (App::getEnv('_APP_USAGE_STATS', 'enabled') == 'enabled') { $period = [ '24h' => [ @@ -230,44 +230,44 @@ App::get('/v1/projects/:projectId/usage') 'group' => '1d', ], ]; - + $client = $register->get('influxdb'); - + $requests = []; $network = []; $functions = []; - + if ($client) { $start = $period[$range]['start']->format(DateTime::RFC3339); $end = $period[$range]['end']->format(DateTime::RFC3339); $database = $client->selectDB('telegraf'); - + // Requests - $result = $database->query('SELECT sum(value) AS "value" FROM "appwrite_usage_requests_all" WHERE time > \''.$start.'\' AND time < \''.$end.'\' AND "metric_type"=\'counter\' AND "project"=\''.$project->getId().'\' GROUP BY time('.$period[$range]['group'].') FILL(null)'); + $result = $database->query('SELECT sum(value) AS "value" FROM "appwrite_usage_requests_all" WHERE time > \'' . $start . '\' AND time < \'' . $end . '\' AND "metric_type"=\'counter\' AND "project"=\'' . $project->getId() . '\' GROUP BY time(' . $period[$range]['group'] . ') FILL(null)'); $points = $result->getPoints(); - + foreach ($points as $point) { $requests[] = [ 'value' => (!empty($point['value'])) ? $point['value'] : 0, 'date' => \strtotime($point['time']), ]; } - + // Network - $result = $database->query('SELECT sum(value) AS "value" FROM "appwrite_usage_network_all" WHERE time > \''.$start.'\' AND time < \''.$end.'\' AND "metric_type"=\'counter\' AND "project"=\''.$project->getId().'\' GROUP BY time('.$period[$range]['group'].') FILL(null)'); + $result = $database->query('SELECT sum(value) AS "value" FROM "appwrite_usage_network_all" WHERE time > \'' . $start . '\' AND time < \'' . $end . '\' AND "metric_type"=\'counter\' AND "project"=\'' . $project->getId() . '\' GROUP BY time(' . $period[$range]['group'] . ') FILL(null)'); $points = $result->getPoints(); - + foreach ($points as $point) { $network[] = [ 'value' => (!empty($point['value'])) ? $point['value'] : 0, 'date' => \strtotime($point['time']), ]; } - + // Functions - $result = $database->query('SELECT sum(value) AS "value" FROM "appwrite_usage_executions_all" WHERE time > \''.$start.'\' AND time < \''.$end.'\' AND "metric_type"=\'counter\' AND "project"=\''.$project->getId().'\' GROUP BY time('.$period[$range]['group'].') FILL(null)'); + $result = $database->query('SELECT sum(value) AS "value" FROM "appwrite_usage_executions_all" WHERE time > \'' . $start . '\' AND time < \'' . $end . '\' AND "metric_type"=\'counter\' AND "project"=\'' . $project->getId() . '\' GROUP BY time(' . $period[$range]['group'] . ') FILL(null)'); $points = $result->getPoints(); - + foreach ($points as $point) { $functions[] = [ 'value' => (!empty($point['value'])) ? $point['value'] : 0, @@ -281,14 +281,13 @@ App::get('/v1/projects/:projectId/usage') $functions = []; } - // Users $projectDB->getCollection([ 'limit' => 0, 'offset' => 0, 'filters' => [ - '$collection=users' + '$collection=users', ], ]); @@ -313,7 +312,7 @@ App::get('/v1/projects/:projectId/usage') 'limit' => 0, 'offset' => 0, 'filters' => [ - '$collection='.$collection['$id'], + '$collection=' . $collection['$id'], ], ]); @@ -369,7 +368,7 @@ App::get('/v1/projects/:projectId/usage') '$collection=files', ], ] - ) + + ) + $projectDB->getCount( [ 'attribute' => 'size', @@ -416,16 +415,16 @@ App::patch('/v1/projects/:projectId') } $project = $dbForConsole->updateDocument('projects', $project->getId(), $project - ->setAttribute('name', $name) - ->setAttribute('description', $description) - ->setAttribute('logo', $logo) - ->setAttribute('url', $url) - ->setAttribute('legalName', $legalName) - ->setAttribute('legalCountry', $legalCountry) - ->setAttribute('legalState', $legalState) - ->setAttribute('legalCity', $legalCity) - ->setAttribute('legalAddress', $legalAddress) - ->setAttribute('legalTaxId', $legalTaxId) + ->setAttribute('name', $name) + ->setAttribute('description', $description) + ->setAttribute('logo', $logo) + ->setAttribute('url', $url) + ->setAttribute('legalName', $legalName) + ->setAttribute('legalCountry', $legalCountry) + ->setAttribute('legalState', $legalState) + ->setAttribute('legalCity', $legalCity) + ->setAttribute('legalAddress', $legalAddress) + ->setAttribute('legalTaxId', $legalTaxId) ); $response->dynamic2($project, Response::MODEL_PROJECT); @@ -458,8 +457,8 @@ App::patch('/v1/projects/:projectId/oauth2') } $project = $dbForConsole->updateDocument('projects', $project->getId(), $project - ->setAttribute('usersOauth2'.\ucfirst($provider).'Appid', $appId) - ->setAttribute('usersOauth2'.\ucfirst($provider).'Secret', $secret) + ->setAttribute('usersOauth2' . \ucfirst($provider) . 'Appid', $appId) + ->setAttribute('usersOauth2' . \ucfirst($provider) . 'Secret', $secret) ); $response->dynamic2($project, Response::MODEL_PROJECT); @@ -490,7 +489,7 @@ App::patch('/v1/projects/:projectId/auth/limit') } $dbForConsole->updateDocument('projects', $project->getId(), $project - ->setAttribute('usersAuthLimit', $limit) + ->setAttribute('usersAuthLimit', $limit) ); $response->dynamic2($project, Response::MODEL_PROJECT); @@ -507,7 +506,7 @@ App::patch('/v1/projects/:projectId/auth/:method') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_PROJECT) ->param('projectId', '', new UID(), 'Project unique ID.') - ->param('method', '', new WhiteList(\array_keys(Config::getParam('auth')), true), 'Auth Method. Possible values: '.implode(',', \array_keys(Config::getParam('auth'))), false) + ->param('method', '', new WhiteList(\array_keys(Config::getParam('auth')), true), 'Auth Method. Possible values: ' . implode(',', \array_keys(Config::getParam('auth'))), false) ->param('status', false, new Boolean(true), 'Set the status of this auth method.') ->inject('response') ->inject('dbForConsole') @@ -525,7 +524,7 @@ App::patch('/v1/projects/:projectId/auth/:method') } $dbForConsole->updateDocument('projects', $project->getId(), $project - ->setAttribute($authKey, $status) + ->setAttribute($authKey, $status) ); $response->dynamic2($project, Response::MODEL_PROJECT); @@ -566,7 +565,7 @@ App::delete('/v1/projects/:projectId') ->setParam('type', DELETE_TYPE_DOCUMENT) ->setParam('document', $project) ; - + if (!$dbForConsole->deleteDocument('teams', $project->getAttribute('teamId', null))) { throw new Exception('Failed to remove project team from DB', 500); } @@ -622,7 +621,7 @@ App::post('/v1/projects/:projectId/webhooks') ]); $project = $dbForConsole->updateDocument('projects', $project->getId(), $project - ->setAttribute('webhooks', $webhook, Document::SET_TYPE_APPEND) + ->setAttribute('webhooks', $webhook, Document::SET_TYPE_APPEND) ); $response->setStatusCode(Response::STATUS_CODE_CREATED); @@ -732,16 +731,16 @@ App::put('/v1/projects/:projectId/webhooks/:webhookId') } $project->findAndReplace('$id', $webhook->getId(), $webhook - ->setAttribute('name', $name) - ->setAttribute('events', $events) - ->setAttribute('url', $url) - ->setAttribute('security', $security) - ->setAttribute('httpUser', $httpUser) - ->setAttribute('httpPass', $httpPass) - , 'webhooks'); + ->setAttribute('name', $name) + ->setAttribute('events', $events) + ->setAttribute('url', $url) + ->setAttribute('security', $security) + ->setAttribute('httpUser', $httpUser) + ->setAttribute('httpPass', $httpPass) + , 'webhooks'); $dbForConsole->updateDocument('projects', $project->getId(), $project); - + $response->dynamic2($webhook, Response::MODEL_WEBHOOK); }); @@ -812,7 +811,7 @@ App::post('/v1/projects/:projectId/keys') ]); $project = $dbForConsole->updateDocument('projects', $project->getId(), $project - ->setAttribute('keys', $key, Document::SET_TYPE_APPEND) + ->setAttribute('keys', $key, Document::SET_TYPE_APPEND) ); $response->setStatusCode(Response::STATUS_CODE_CREATED); @@ -835,7 +834,7 @@ App::get('/v1/projects/:projectId/keys') ->action(function ($projectId, $response, $dbForConsole) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForConsole */ - + $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { @@ -913,12 +912,12 @@ App::put('/v1/projects/:projectId/keys/:keyId') } $project->findAndReplace('$id', $key->getId(), $key - ->setAttribute('name', $name) - ->setAttribute('scopes', $scopes) - , 'keys'); + ->setAttribute('name', $name) + ->setAttribute('scopes', $scopes) + , 'keys'); $dbForConsole->updateDocument('projects', $project->getId(), $project); - + $response->dynamic2($key, Response::MODEL_KEY); }); @@ -1011,7 +1010,7 @@ App::post('/v1/projects/:projectId/tasks') ]); $project = $dbForConsole->updateDocument('projects', $project->getId(), $project - ->setAttribute('tasks', $task, Document::SET_TYPE_APPEND) + ->setAttribute('tasks', $task, Document::SET_TYPE_APPEND) ); if ($next) { @@ -1131,18 +1130,18 @@ App::put('/v1/projects/:projectId/tasks/:taskId') $security = ($security === '1' || $security === 'true' || $security === 1 || $security === true); $project->findAndReplace('$id', $task->getId(), $task - ->setAttribute('name', $name) - ->setAttribute('status', $status) - ->setAttribute('schedule', $schedule) - ->setAttribute('updated', \time()) - ->setAttribute('next', $next) - ->setAttribute('security', $security) - ->setAttribute('httpMethod', $httpMethod) - ->setAttribute('httpUrl', $httpUrl) - ->setAttribute('httpHeaders', $httpHeaders) - ->setAttribute('httpUser', $httpUser) - ->setAttribute('httpPass', $httpPass) - , 'tasks'); + ->setAttribute('name', $name) + ->setAttribute('status', $status) + ->setAttribute('schedule', $schedule) + ->setAttribute('updated', \time()) + ->setAttribute('next', $next) + ->setAttribute('security', $security) + ->setAttribute('httpMethod', $httpMethod) + ->setAttribute('httpUrl', $httpUrl) + ->setAttribute('httpHeaders', $httpHeaders) + ->setAttribute('httpUser', $httpUser) + ->setAttribute('httpPass', $httpPass) + , 'tasks'); $dbForConsole->updateDocument('projects', $project->getId(), $project); @@ -1227,13 +1226,13 @@ App::post('/v1/projects/:projectId/platforms') ]); $project = $dbForConsole->updateDocument('projects', $project->getId(), $project - ->setAttribute('platforms', $platform, Document::SET_TYPE_APPEND) + ->setAttribute('platforms', $platform, Document::SET_TYPE_APPEND) ); $response->setStatusCode(Response::STATUS_CODE_CREATED); $response->dynamic2($platform, Response::MODEL_PLATFORM); }); - + App::get('/v1/projects/:projectId/platforms') ->desc('List Platforms') ->groups(['api', 'projects']) @@ -1341,12 +1340,12 @@ App::put('/v1/projects/:projectId/platforms/:platformId') ; $project->findAndReplace('$id', $platform->getId(), $platform - ->setAttribute('name', $name) - ->setAttribute('dateUpdated', \time()) - ->setAttribute('key', $key) - ->setAttribute('store', $store) - ->setAttribute('hostname', $hostname) - , 'platforms'); + ->setAttribute('name', $name) + ->setAttribute('dateUpdated', \time()) + ->setAttribute('key', $key) + ->setAttribute('store', $store) + ->setAttribute('hostname', $hostname) + , 'platforms'); $dbForConsole->updateDocument('projects', $project->getId(), $project); @@ -1420,7 +1419,7 @@ App::post('/v1/projects/:projectId/domains') $target = new Domain(App::getEnv('_APP_DOMAIN_TARGET', '')); if (!$target->isKnown() || $target->isTest()) { - throw new Exception('Unreachable CNAME target ('.$target->get().'), please use a domain with a public suffix.', 500); + throw new Exception('Unreachable CNAME target (' . $target->get() . '), please use a domain with a public suffix.', 500); } $domain = new Domain($domain); @@ -1436,7 +1435,7 @@ App::post('/v1/projects/:projectId/domains') ]); $project = $dbForConsole->updateDocument('projects', $project->getId(), $project - ->setAttribute('domains', $domain, Document::SET_TYPE_APPEND) + ->setAttribute('domains', $domain, Document::SET_TYPE_APPEND) ); $response->setStatusCode(Response::STATUS_CODE_CREATED); @@ -1467,7 +1466,7 @@ App::get('/v1/projects/:projectId/domains') } $domains = $project->getAttribute('domains', []); - + $response->dynamic2(new Document([ 'domains' => $domains, 'sum' => count($domains), @@ -1540,7 +1539,7 @@ App::patch('/v1/projects/:projectId/domains/:domainId/verification') $target = new Domain(App::getEnv('_APP_DOMAIN_TARGET', '')); if (!$target->isKnown() || $target->isTest()) { - throw new Exception('Unreachable CNAME target ('.$target->get().'), please use a domain with a public suffix.', 500); + throw new Exception('Unreachable CNAME target (' . $target->get() . '), please use a domain with a public suffix.', 500); } if ($domain->getAttribute('verification') === true) { @@ -1554,8 +1553,8 @@ App::patch('/v1/projects/:projectId/domains/:domainId/verification') } $project->findAndReplace('$id', $domain->getId(), $domain - ->setAttribute('verification', true) - , 'domains'); + ->setAttribute('verification', true) + , 'domains'); $dbForConsole->updateDocument('projects', $project->getId(), $project); @@ -1606,4 +1605,4 @@ App::delete('/v1/projects/:projectId/domains/:domainId') ; $response->noContent(); - }); \ No newline at end of file + }); diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 618299eed9..355bfd9c50 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -13,7 +13,6 @@ use Utopia\Validator\Text; use Utopia\Validator\Range; use Utopia\Validator\Boolean; use Utopia\Audit\Audit; -use Utopia\Audit\Adapters\MySQL as AuditAdapter; use Utopia\Database\Document; use Utopia\Database\Exception\Duplicate; use Utopia\Database\Validator\UID; @@ -212,12 +211,10 @@ App::get('/v1/users/:userId/logs') ->label('sdk.response.model', Response::MODEL_LOG_LIST) ->param('userId', '', new UID(), 'User unique ID.') ->inject('response') - ->inject('register') - ->inject('project') ->inject('dbForInternal') ->inject('locale') ->inject('geodb') - ->action(function ($userId, $response, $register, $project, $dbForInternal, $locale, $geodb) { + ->action(function ($userId, $response, $dbForInternal, $locale, $geodb) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Registry\Registry $register */ /** @var Appwrite\Database\Document $project */ @@ -231,10 +228,7 @@ App::get('/v1/users/:userId/logs') throw new Exception('User not found', 404); } - $adapter = new AuditAdapter($register->get('db')); - $adapter->setNamespace('app_'.$project->getId()); - - $audit = new Audit($adapter); + $audit = new Audit($dbForInternal); $countries = $locale->getText('countries'); diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index 18aaea5b78..cb0a9e2e42 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -9,7 +9,7 @@ use Utopia\Abuse\Adapters\TimeLimit; use Utopia\Storage\Device\Local; use Utopia\Storage\Storage; -App::init(function ($utopia, $request, $response, $project, $user, $register, $events, $audits, $usage, $deletes) { +App::init(function ($utopia, $request, $response, $project, $user, $register, $events, $audits, $usage, $deletes, $dbForInternal) { /** @var Utopia\App $utopia */ /** @var Utopia\Swoole\Request $request */ /** @var Appwrite\Utopia\Response $response */ @@ -21,6 +21,7 @@ App::init(function ($utopia, $request, $response, $project, $user, $register, $e /** @var Appwrite\Event\Event $usage */ /** @var Appwrite\Event\Event $deletes */ /** @var Appwrite\Event\Event $functions */ + /** @var Utopia\Database\Database $dbForInternal */ Storage::setDevice('files', new Local(APP_STORAGE_UPLOADS.'/app-'.$project->getId())); Storage::setDevice('functions', new Local(APP_STORAGE_FUNCTIONS.'/app-'.$project->getId())); @@ -31,47 +32,44 @@ App::init(function ($utopia, $request, $response, $project, $user, $register, $e throw new Exception('Missing or unknown project ID', 400); } - // /* - // * Abuse Check - // */ - // $timeLimit = new TimeLimit($route->getLabel('abuse-key', 'url:{url},ip:{ip}'), $route->getLabel('abuse-limit', 0), $route->getLabel('abuse-time', 3600), function () use ($register) { - // return $register->get('db'); - // }); - // $timeLimit->setNamespace('app_'.$project->getId()); - // $timeLimit - // ->setParam('{userId}', $user->getId()) - // ->setParam('{userAgent}', $request->getUserAgent('')) - // ->setParam('{ip}', $request->getIP()) - // ->setParam('{url}', $request->getHostname().$route->getURL()) - // ; + /* + * Abuse Check + */ + $timeLimit = new TimeLimit($route->getLabel('abuse-key', 'url:{url},ip:{ip}'), $route->getLabel('abuse-limit', 0), $route->getLabel('abuse-time', 3600), $dbForInternal); + $timeLimit + ->setParam('{userId}', $user->getId()) + ->setParam('{userAgent}', $request->getUserAgent('')) + ->setParam('{ip}', $request->getIP()) + ->setParam('{url}', $request->getHostname().$route->getURL()) + ; - // //TODO make sure we get array here + //TODO make sure we get array here - // foreach ($request->getParams() as $key => $value) { // Set request params as potential abuse keys - // if(!empty($value)) { - // $timeLimit->setParam('{param-'.$key.'}', (\is_array($value)) ? \json_encode($value) : $value); - // } - // } + foreach ($request->getParams() as $key => $value) { // Set request params as potential abuse keys + if(!empty($value)) { + $timeLimit->setParam('{param-'.$key.'}', (\is_array($value)) ? \json_encode($value) : $value); + } + } - // $abuse = new Abuse($timeLimit); + $abuse = new Abuse($timeLimit); - // if ($timeLimit->limit()) { - // $response - // ->addHeader('X-RateLimit-Limit', $timeLimit->limit()) - // ->addHeader('X-RateLimit-Remaining', $timeLimit->remaining()) - // ->addHeader('X-RateLimit-Reset', $timeLimit->time() + $route->getLabel('abuse-time', 3600)) - // ; - // } + if ($timeLimit->limit()) { + $response + ->addHeader('X-RateLimit-Limit', $timeLimit->limit()) + ->addHeader('X-RateLimit-Remaining', $timeLimit->remaining()) + ->addHeader('X-RateLimit-Reset', $timeLimit->time() + $route->getLabel('abuse-time', 3600)) + ; + } - // $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::$roles); - // $isAppUser = Auth::isAppUser(Authorization::$roles); + $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::$roles); + $isAppUser = Auth::isAppUser(Authorization::$roles); - // if (($abuse->check() // Route is rate-limited - // && App::getEnv('_APP_OPTIONS_ABUSE', 'enabled') !== 'disabled') // Abuse is not diabled - // && (!$isAppUser && !$isPrivilegedUser)) // User is not an admin or API key - // { - // throw new Exception('Too many requests', 429); - // } + if (($abuse->check() // Route is rate-limited + && App::getEnv('_APP_OPTIONS_ABUSE', 'enabled') !== 'disabled') // Abuse is not diabled + && (!$isAppUser && !$isPrivilegedUser)) // User is not an admin or API key + { + throw new Exception('Too many requests', 429); + } /* * Background Jobs @@ -111,7 +109,7 @@ App::init(function ($utopia, $request, $response, $project, $user, $register, $e ->setParam('projectId', $project->getId()) ; -}, ['utopia', 'request', 'response', 'project', 'user', 'register', 'events', 'audits', 'usage', 'deletes'], 'api'); +}, ['utopia', 'request', 'response', 'project', 'user', 'register', 'events', 'audits', 'usage', 'deletes', 'dbForInternal'], 'api'); App::init(function ($utopia, $request, $response, $project, $user) { diff --git a/app/workers/audits.php b/app/workers/audits.php index d6027dd2e3..237b44258b 100644 --- a/app/workers/audits.php +++ b/app/workers/audits.php @@ -2,7 +2,11 @@ use Utopia\Audit\Audit; use Utopia\Audit\Adapters\MySQL as AuditAdapter; +use Utopia\Cache\Adapter\Redis; +use Utopia\Cache\Cache; use Utopia\CLI\Console; +use Utopia\Database\Adapter\MariaDB; +use Utopia\Database\Database; require_once __DIR__.'/../init.php'; @@ -31,10 +35,11 @@ class AuditsV1 $data = $this->args['data']; $db = $register->get('db', true); - $adapter = new AuditAdapter($db); - $adapter->setNamespace('app_'.$projectId); + $cache = new Cache(new Redis($register->get('cache'))); + $dbForInternal = new Database(new MariaDB($db), $cache); + $dbForInternal->setNamespace('project_'.$projectId.'_internal'); - $audit = new Audit($adapter); + $audit = new Audit($dbForInternal); $audit->log($userId, $event, $resource, $userAgent, $ip, '', $data); } diff --git a/app/workers/deletes.php b/app/workers/deletes.php index 5316783c49..b83a7c4c23 100644 --- a/app/workers/deletes.php +++ b/app/workers/deletes.php @@ -1,6 +1,8 @@ get('db'); - }); - - $this->deleteForProjectIds(function($projectId) use ($timeLimit, $timestamp){ - $timeLimit->setNamespace('app_'.$projectId); + + $this->deleteForProjectIds(function($projectId) use ($timestamp){ + $timeLimit = new TimeLimit("", 0, 1, $this->getInternalDB($projectId)); $abuse = new Abuse($timeLimit); $status = $abuse->cleanup($timestamp); @@ -189,9 +190,7 @@ class DeletesV1 throw new Exception('Failed to delete audit logs. No timestamp provided'); } $this->deleteForProjectIds(function($projectId) use ($register, $timestamp){ - $adapter = new AuditAdapter($register->get('db')); - $adapter->setNamespace('app_'.$projectId); - $audit = new Audit($adapter); + $audit = new Audit($this->getInternalDB($projectId)); $status = $audit->cleanup($timestamp); if (!$status) { throw new Exception('Failed to delete Audit logs for project'.$projectId); @@ -375,4 +374,18 @@ class DeletesV1 return $projectDB; } + + /** + * @return Database2 + */ + protected function getInternalDB($projectId): Database2 + { + global $register; + + $cache = new Cache(new RedisCache($register->get('cache'))); + $dbForInternal = new Database2(new MariaDB($register->get('db')), $cache); + $dbForInternal->setNamespace('project_'.$projectId.'_internal'); // Main DB + + return $dbForInternal; + } } \ No newline at end of file diff --git a/composer.json b/composer.json index c5cf7a271d..7b30b3209e 100644 --- a/composer.json +++ b/composer.json @@ -39,9 +39,9 @@ "appwrite/php-runtimes": "0.2.*", "utopia-php/framework": "0.14.*", - "utopia-php/abuse": "0.4.*", + "utopia-php/abuse": "dev-feat-utopia-db-integration", "utopia-php/analytics": "0.2.*", - "utopia-php/audit": "0.5.*", + "utopia-php/audit": "dev-feat-utopia-db-integration", "utopia-php/cache": "0.4.*", "utopia-php/cli": "0.11.*", "utopia-php/config": "0.2.*", @@ -62,6 +62,16 @@ "adhocore/jwt": "1.1.2", "slickdeals/statsd": "3.0.2" }, + "repositories": [ + { + "type": "git", + "url": "https://github.com/lohanidamodar/audit" + }, + { + "type": "git", + "url": "https://github.com/lohanidamodar/abuse" + } + ], "require-dev": { "appwrite/sdk-generator": "0.10.6", "swoole/ide-helper": "4.6.6", diff --git a/composer.lock b/composer.lock index b2d89a1e6a..76df100aff 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "5cf39daf305902a168233757d4a00066", + "content-hash": "3eb7dc73e9524209a38bd6c33a540732", "packages": [ { "name": "adhocore/jwt", @@ -1603,21 +1603,22 @@ }, { "name": "utopia-php/abuse", - "version": "0.4.0", + "version": "dev-feat-utopia-db-integration", "source": { "type": "git", - "url": "https://github.com/utopia-php/abuse.git", - "reference": "2b8cc40a67c045c137b44d1a11326f494acf50a4" + "url": "https://github.com/lohanidamodar/abuse.git", + "reference": "525cdf6674e11a7229cba31941915ac50a3e575a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/abuse/zipball/2b8cc40a67c045c137b44d1a11326f494acf50a4", - "reference": "2b8cc40a67c045c137b44d1a11326f494acf50a4", + "url": "https://api.github.com/repos/lohanidamodar/abuse/zipball/525cdf6674e11a7229cba31941915ac50a3e575a", + "reference": "525cdf6674e11a7229cba31941915ac50a3e575a", "shasum": "" }, "require": { "ext-pdo": "*", - "php": ">=7.4" + "php": ">=7.4", + "utopia-php/database": "0.2.*" }, "require-dev": { "phpunit/phpunit": "^9.4", @@ -1629,7 +1630,6 @@ "Utopia\\Abuse\\": "src/Abuse" } }, - "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], @@ -1641,17 +1641,16 @@ ], "description": "A simple abuse library to manage application usage limits", "keywords": [ - "Abuse", + "abuse", "framework", "php", "upf", "utopia" ], "support": { - "issues": "https://github.com/utopia-php/abuse/issues", - "source": "https://github.com/utopia-php/abuse/tree/0.4.0" + "source": "https://github.com/lohanidamodar/abuse/tree/feat-utopia-db-integration" }, - "time": "2021-03-17T20:21:24+00:00" + "time": "2021-06-04T08:40:29+00:00" }, { "name": "utopia-php/analytics", @@ -1710,21 +1709,22 @@ }, { "name": "utopia-php/audit", - "version": "0.5.1", + "version": "dev-feat-utopia-db-integration", "source": { "type": "git", - "url": "https://github.com/utopia-php/audit.git", - "reference": "154a850170a58667a15e4b65fbabb6cd0b709dd9" + "url": "https://github.com/lohanidamodar/audit.git", + "reference": "3421b6a54740fbb29570004848cdc0b818717e83" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/audit/zipball/154a850170a58667a15e4b65fbabb6cd0b709dd9", - "reference": "154a850170a58667a15e4b65fbabb6cd0b709dd9", + "url": "https://api.github.com/repos/lohanidamodar/audit/zipball/3421b6a54740fbb29570004848cdc0b818717e83", + "reference": "3421b6a54740fbb29570004848cdc0b818717e83", "shasum": "" }, "require": { "ext-pdo": "*", - "php": ">=7.1" + "php": ">=7.4", + "utopia-php/database": "0.2.*" }, "require-dev": { "phpunit/phpunit": "^9.3", @@ -1736,7 +1736,6 @@ "Utopia\\Audit\\": "src/Audit" } }, - "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], @@ -1748,17 +1747,16 @@ ], "description": "A simple audit library to manage application users logs", "keywords": [ - "Audit", + "audit", "framework", "php", "upf", "utopia" ], "support": { - "issues": "https://github.com/utopia-php/audit/issues", - "source": "https://github.com/utopia-php/audit/tree/0.5.1" + "source": "https://github.com/lohanidamodar/audit/tree/feat-utopia-db-integration" }, - "time": "2020-12-21T17:28:53+00:00" + "time": "2021-06-04T08:41:19+00:00" }, { "name": "utopia-php/cache", @@ -5252,20 +5250,21 @@ }, { "name": "symfony/console", - "version": "v5.2.8", + "version": "v5.3.0", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "864568fdc0208b3eba3638b6000b69d2386e6768" + "reference": "058553870f7809087fa80fa734704a21b9bcaeb2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/864568fdc0208b3eba3638b6000b69d2386e6768", - "reference": "864568fdc0208b3eba3638b6000b69d2386e6768", + "url": "https://api.github.com/repos/symfony/console/zipball/058553870f7809087fa80fa734704a21b9bcaeb2", + "reference": "058553870f7809087fa80fa734704a21b9bcaeb2", "shasum": "" }, "require": { "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1", "symfony/polyfill-mbstring": "~1.0", "symfony/polyfill-php73": "^1.8", "symfony/polyfill-php80": "^1.15", @@ -5329,7 +5328,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.2.8" + "source": "https://github.com/symfony/console/tree/v5.3.0" }, "funding": [ { @@ -5345,7 +5344,74 @@ "type": "tidelift" } ], - "time": "2021-05-11T15:45:21+00:00" + "time": "2021-05-26T17:43:10+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v2.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "5f38c8804a9e97d23e0c8d63341088cd8a22d627" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/5f38c8804a9e97d23e0c8d63341088cd8a22d627", + "reference": "5f38c8804a9e97d23e0c8d63341088cd8a22d627", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.4-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v2.4.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-03-23T23:28:01+00:00" }, { "name": "symfony/polyfill-intl-grapheme", @@ -5752,16 +5818,16 @@ }, { "name": "symfony/string", - "version": "v5.2.8", + "version": "v5.3.0", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "01b35eb64cac8467c3f94cd0ce2d0d376bb7d1db" + "reference": "a9a0f8b6aafc5d2d1c116dcccd1573a95153515b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/01b35eb64cac8467c3f94cd0ce2d0d376bb7d1db", - "reference": "01b35eb64cac8467c3f94cd0ce2d0d376bb7d1db", + "url": "https://api.github.com/repos/symfony/string/zipball/a9a0f8b6aafc5d2d1c116dcccd1573a95153515b", + "reference": "a9a0f8b6aafc5d2d1c116dcccd1573a95153515b", "shasum": "" }, "require": { @@ -5815,7 +5881,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v5.2.8" + "source": "https://github.com/symfony/string/tree/v5.3.0" }, "funding": [ { @@ -5831,7 +5897,7 @@ "type": "tidelift" } ], - "time": "2021-05-10T14:56:10+00:00" + "time": "2021-05-26T17:43:10+00:00" }, { "name": "theseer/tokenizer", @@ -6120,7 +6186,10 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": { + "utopia-php/abuse": 20, + "utopia-php/audit": 20 + }, "prefer-stable": false, "prefer-lowest": false, "platform": { @@ -6143,4 +6212,4 @@ "php": "8.0" }, "plugin-api-version": "2.0.0" -} \ No newline at end of file +} From ee6798666267da32daaaf760ea0c6320006cc0bc Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 7 Jun 2021 11:37:40 +0545 Subject: [PATCH 14/52] db setup --- app/controllers/api/projects.php | 8 +++++++ composer.lock | 36 ++++++++------------------------ 2 files changed, 17 insertions(+), 27 deletions(-) diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index 6af20880a9..897eafa51e 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -20,6 +20,8 @@ use Utopia\Validator\Integer; use Utopia\Validator\Range; use Utopia\Validator\Text; use Utopia\Validator\WhiteList; +use Utopia\Audit\Audit; +use Utopia\Abuse\Adapters\TimeLimit; App::post('/v1/projects') ->desc('Create Project') @@ -92,6 +94,12 @@ App::post('/v1/projects') $dbForExternal->setNamespace('project_' . $project->getId() . '_external'); $dbForExternal->create(); + $audit = new Audit($dbForInternal); + $audit->setup(); + + $adapter = new TimeLimit("", 0, 1, $dbForInternal); + $adapter->setup(); + foreach ($collections as $key => $collection) { $dbForInternal->createCollection($key); diff --git a/composer.lock b/composer.lock index 76df100aff..cc7393c775 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "3eb7dc73e9524209a38bd6c33a540732", + "content-hash": "e3617b2fd699e599dc2c1d7ed74c9f44", "packages": [ { "name": "adhocore/jwt", @@ -1606,15 +1606,9 @@ "version": "dev-feat-utopia-db-integration", "source": { "type": "git", - "url": "https://github.com/lohanidamodar/abuse.git", + "url": "https://github.com/lohanidamodar/abuse", "reference": "525cdf6674e11a7229cba31941915ac50a3e575a" }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/lohanidamodar/abuse/zipball/525cdf6674e11a7229cba31941915ac50a3e575a", - "reference": "525cdf6674e11a7229cba31941915ac50a3e575a", - "shasum": "" - }, "require": { "ext-pdo": "*", "php": ">=7.4", @@ -1647,9 +1641,6 @@ "upf", "utopia" ], - "support": { - "source": "https://github.com/lohanidamodar/abuse/tree/feat-utopia-db-integration" - }, "time": "2021-06-04T08:40:29+00:00" }, { @@ -1712,15 +1703,9 @@ "version": "dev-feat-utopia-db-integration", "source": { "type": "git", - "url": "https://github.com/lohanidamodar/audit.git", + "url": "https://github.com/lohanidamodar/audit", "reference": "3421b6a54740fbb29570004848cdc0b818717e83" }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/lohanidamodar/audit/zipball/3421b6a54740fbb29570004848cdc0b818717e83", - "reference": "3421b6a54740fbb29570004848cdc0b818717e83", - "shasum": "" - }, "require": { "ext-pdo": "*", "php": ">=7.4", @@ -1753,9 +1738,6 @@ "upf", "utopia" ], - "support": { - "source": "https://github.com/lohanidamodar/audit/tree/feat-utopia-db-integration" - }, "time": "2021-06-04T08:41:19+00:00" }, { @@ -5089,16 +5071,16 @@ }, { "name": "sebastian/type", - "version": "2.3.1", + "version": "2.3.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "81cd61ab7bbf2de744aba0ea61fae32f721df3d2" + "reference": "0d1c587401514d17e8f9258a27e23527cb1b06c1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/81cd61ab7bbf2de744aba0ea61fae32f721df3d2", - "reference": "81cd61ab7bbf2de744aba0ea61fae32f721df3d2", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/0d1c587401514d17e8f9258a27e23527cb1b06c1", + "reference": "0d1c587401514d17e8f9258a27e23527cb1b06c1", "shasum": "" }, "require": { @@ -5133,7 +5115,7 @@ "homepage": "https://github.com/sebastianbergmann/type", "support": { "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/2.3.1" + "source": "https://github.com/sebastianbergmann/type/tree/2.3.2" }, "funding": [ { @@ -5141,7 +5123,7 @@ "type": "github" } ], - "time": "2020-10-26T13:18:59+00:00" + "time": "2021-06-04T13:02:07+00:00" }, { "name": "sebastian/version", From 5201e488152b860501ab4e42db5ff8e4dc32dd80 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 7 Jun 2021 12:02:46 +0545 Subject: [PATCH 15/52] adding image crop gravity support --- app/controllers/api/storage.php | 5 +++-- composer.json | 2 +- composer.lock | 38 ++++++++++++++++----------------- 3 files changed, 23 insertions(+), 22 deletions(-) diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index b3060eaf4b..d7b214dd22 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -242,6 +242,7 @@ App::get('/v1/storage/files/:fileId/preview') ->param('fileId', '', new UID(), 'File unique ID') ->param('width', 0, new Range(0, 4000), 'Resize preview image width, Pass an integer between 0 to 4000.', true) ->param('height', 0, new Range(0, 4000), 'Resize preview image height, Pass an integer between 0 to 4000.', true) + ->param('gravity', Image::GRAVITY_CENTER, new Range(0, 8), 'Image crop gravity', true) ->param('quality', 100, new Range(0, 100), 'Preview image quality. Pass an integer between 0 to 100. Defaults to 100.', true) ->param('borderWidth', 0, new Range(0, 100), 'Preview image border in pixels. Pass an integer between 0 to 100. Defaults to 0.', true) ->param('borderColor', '', new HexColor(), 'Preview image border color. Use a valid HEX color, no # is needed for prefix.', true) @@ -254,7 +255,7 @@ App::get('/v1/storage/files/:fileId/preview') ->inject('response') ->inject('project') ->inject('projectDB') - ->action(function ($fileId, $width, $height, $quality, $borderWidth, $borderColor, $borderRadius, $opacity, $rotation, $background, $output, $request, $response, $project, $projectDB) { + ->action(function ($fileId, $width, $height, $gravity, $quality, $borderWidth, $borderColor, $borderRadius, $opacity, $rotation, $background, $output, $request, $response, $project, $projectDB) { /** @var Utopia\Swoole\Request $request */ /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Document $project */ @@ -342,7 +343,7 @@ App::get('/v1/storage/files/:fileId/preview') $image = new Image($source); - $image->crop((int) $width, (int) $height); + $image->crop((int) $width, (int) $height, (int) $gravity); if (!empty($opacity) || $opacity==0) { $image->setOpacity($opacity); diff --git a/composer.json b/composer.json index ee9942a9f3..c27cf8276a 100644 --- a/composer.json +++ b/composer.json @@ -51,7 +51,7 @@ "utopia-php/domains": "1.1.*", "utopia-php/swoole": "0.2.*", "utopia-php/storage": "0.5.*", - "utopia-php/image": "0.2.*", + "utopia-php/image": "0.3.*", "resque/php-resque": "1.3.6", "matomo/device-detector": "4.2.2", "dragonmantank/cron-expression": "3.1.0", diff --git a/composer.lock b/composer.lock index 1a89b21c8b..82b95e2d26 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": "9a72955402438e63ec6101723c33f544", + "content-hash": "2d32e708dd70ab32d06b912e74b1194a", "packages": [ { "name": "adhocore/jwt", @@ -1324,16 +1324,16 @@ }, { "name": "utopia-php/abuse", - "version": "0.4.0", + "version": "0.4.1", "source": { "type": "git", "url": "https://github.com/utopia-php/abuse.git", - "reference": "2b8cc40a67c045c137b44d1a11326f494acf50a4" + "reference": "8b7973aae4b02489bd22ffea45b985608f13b6d9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/abuse/zipball/2b8cc40a67c045c137b44d1a11326f494acf50a4", - "reference": "2b8cc40a67c045c137b44d1a11326f494acf50a4", + "url": "https://api.github.com/repos/utopia-php/abuse/zipball/8b7973aae4b02489bd22ffea45b985608f13b6d9", + "reference": "8b7973aae4b02489bd22ffea45b985608f13b6d9", "shasum": "" }, "require": { @@ -1370,9 +1370,9 @@ ], "support": { "issues": "https://github.com/utopia-php/abuse/issues", - "source": "https://github.com/utopia-php/abuse/tree/0.4.0" + "source": "https://github.com/utopia-php/abuse/tree/0.4.1" }, - "time": "2021-03-17T20:21:24+00:00" + "time": "2021-06-05T14:31:33+00:00" }, { "name": "utopia-php/analytics", @@ -1742,16 +1742,16 @@ }, { "name": "utopia-php/image", - "version": "0.2.1", + "version": "0.3.0", "source": { "type": "git", "url": "https://github.com/utopia-php/image.git", - "reference": "0754955a165483852184d1215cc3bf659432d23a" + "reference": "7761ff565e505bb3ddb9cfa05b7e1efddf3bebaa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/image/zipball/0754955a165483852184d1215cc3bf659432d23a", - "reference": "0754955a165483852184d1215cc3bf659432d23a", + "url": "https://api.github.com/repos/utopia-php/image/zipball/7761ff565e505bb3ddb9cfa05b7e1efddf3bebaa", + "reference": "7761ff565e505bb3ddb9cfa05b7e1efddf3bebaa", "shasum": "" }, "require": { @@ -1789,9 +1789,9 @@ ], "support": { "issues": "https://github.com/utopia-php/image/issues", - "source": "https://github.com/utopia-php/image/tree/0.2.1" + "source": "https://github.com/utopia-php/image/tree/0.3.0" }, - "time": "2021-04-13T07:47:24+00:00" + "time": "2021-06-02T07:08:04+00:00" }, { "name": "utopia-php/locale", @@ -4823,16 +4823,16 @@ }, { "name": "sebastian/type", - "version": "2.3.1", + "version": "2.3.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "81cd61ab7bbf2de744aba0ea61fae32f721df3d2" + "reference": "0d1c587401514d17e8f9258a27e23527cb1b06c1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/81cd61ab7bbf2de744aba0ea61fae32f721df3d2", - "reference": "81cd61ab7bbf2de744aba0ea61fae32f721df3d2", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/0d1c587401514d17e8f9258a27e23527cb1b06c1", + "reference": "0d1c587401514d17e8f9258a27e23527cb1b06c1", "shasum": "" }, "require": { @@ -4867,7 +4867,7 @@ "homepage": "https://github.com/sebastianbergmann/type", "support": { "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/2.3.1" + "source": "https://github.com/sebastianbergmann/type/tree/2.3.2" }, "funding": [ { @@ -4875,7 +4875,7 @@ "type": "github" } ], - "time": "2020-10-26T13:18:59+00:00" + "time": "2021-06-04T13:02:07+00:00" }, { "name": "sebastian/version", From 4b9be0f741d1bcfcfee863f12c6275bf4a5b8c67 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Tue, 8 Jun 2021 12:25:09 +0545 Subject: [PATCH 16/52] composer update --- composer.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.lock b/composer.lock index 92e91a62a7..26b8c6c85b 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": "6704f7df5ffe0baac3633dc8e683ed78", + "content-hash": "399d2426ca92e04b6d6fb84a91c316c3", "packages": [ { "name": "adhocore/jwt", From 4fb54ac87b2d793e8dbb88690bd71815cfc1abbd Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Wed, 9 Jun 2021 12:26:39 +0545 Subject: [PATCH 17/52] fix crop gravity param type --- app/controllers/api/storage.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index d7b214dd22..f4f00cbea6 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -242,7 +242,7 @@ App::get('/v1/storage/files/:fileId/preview') ->param('fileId', '', new UID(), 'File unique ID') ->param('width', 0, new Range(0, 4000), 'Resize preview image width, Pass an integer between 0 to 4000.', true) ->param('height', 0, new Range(0, 4000), 'Resize preview image height, Pass an integer between 0 to 4000.', true) - ->param('gravity', Image::GRAVITY_CENTER, new Range(0, 8), 'Image crop gravity', true) + ->param('gravity', Image::GRAVITY_CENTER, new WhiteList(Image::GRAVITY_CENTER, Image::GRAVITY_NORTH, Image::GRAVITY_NORTHWEST, Image::GRAVITY_NORTHEAST, Image::GRAVITY_WEST, Image::GRAVITY_EAST, Image::GRAVITY_SOUTHWEST, Image::GRAVITY_SOUTH, Image::GRAVITY_SOUTHEAST), 'Image crop gravity', true) ->param('quality', 100, new Range(0, 100), 'Preview image quality. Pass an integer between 0 to 100. Defaults to 100.', true) ->param('borderWidth', 0, new Range(0, 100), 'Preview image border in pixels. Pass an integer between 0 to 100. Defaults to 0.', true) ->param('borderColor', '', new HexColor(), 'Preview image border color. Use a valid HEX color, no # is needed for prefix.', true) From 9a9a309ce22231c554da8daace9fa28d2996c96f Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Wed, 9 Jun 2021 12:30:02 +0545 Subject: [PATCH 18/52] Update app/controllers/api/projects.php Co-authored-by: Eldad A. Fux --- app/controllers/api/projects.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index a58af2afe8..174b18f98d 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -97,7 +97,7 @@ App::post('/v1/projects') $audit = new Audit($dbForInternal); $audit->setup(); - $adapter = new TimeLimit("", 0, 1, $dbForInternal); + $adapter = new TimeLimit("", 0, 1, $dbForInternal); $adapter->setup(); foreach ($collections as $key => $collection) { From 3c29e0da95d5b942c033b91c665752b8f1c66133 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Wed, 9 Jun 2021 13:06:49 +0545 Subject: [PATCH 19/52] update image library --- composer.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.lock b/composer.lock index 26b8c6c85b..053de4bab1 100644 --- a/composer.lock +++ b/composer.lock @@ -1742,16 +1742,16 @@ }, { "name": "utopia-php/image", - "version": "0.3.0", + "version": "0.3.1", "source": { "type": "git", "url": "https://github.com/utopia-php/image.git", - "reference": "7761ff565e505bb3ddb9cfa05b7e1efddf3bebaa" + "reference": "20849a3a55790bd6eb3decde9f9708f04d58e489" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/image/zipball/7761ff565e505bb3ddb9cfa05b7e1efddf3bebaa", - "reference": "7761ff565e505bb3ddb9cfa05b7e1efddf3bebaa", + "url": "https://api.github.com/repos/utopia-php/image/zipball/20849a3a55790bd6eb3decde9f9708f04d58e489", + "reference": "20849a3a55790bd6eb3decde9f9708f04d58e489", "shasum": "" }, "require": { @@ -1789,9 +1789,9 @@ ], "support": { "issues": "https://github.com/utopia-php/image/issues", - "source": "https://github.com/utopia-php/image/tree/0.3.0" + "source": "https://github.com/utopia-php/image/tree/0.3.1" }, - "time": "2021-06-02T07:08:04+00:00" + "time": "2021-06-09T07:12:35+00:00" }, { "name": "utopia-php/locale", From e1133b665d2ff2afb55581a21ae233108e81c3e0 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Wed, 9 Jun 2021 14:22:20 +0545 Subject: [PATCH 20/52] fix whitelist issue --- app/controllers/api/storage.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index f4f00cbea6..68edc774ee 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -242,7 +242,7 @@ App::get('/v1/storage/files/:fileId/preview') ->param('fileId', '', new UID(), 'File unique ID') ->param('width', 0, new Range(0, 4000), 'Resize preview image width, Pass an integer between 0 to 4000.', true) ->param('height', 0, new Range(0, 4000), 'Resize preview image height, Pass an integer between 0 to 4000.', true) - ->param('gravity', Image::GRAVITY_CENTER, new WhiteList(Image::GRAVITY_CENTER, Image::GRAVITY_NORTH, Image::GRAVITY_NORTHWEST, Image::GRAVITY_NORTHEAST, Image::GRAVITY_WEST, Image::GRAVITY_EAST, Image::GRAVITY_SOUTHWEST, Image::GRAVITY_SOUTH, Image::GRAVITY_SOUTHEAST), 'Image crop gravity', true) + ->param('gravity', Image::GRAVITY_CENTER, new WhiteList([Image::GRAVITY_CENTER, Image::GRAVITY_NORTH, Image::GRAVITY_NORTHWEST, Image::GRAVITY_NORTHEAST, Image::GRAVITY_WEST, Image::GRAVITY_EAST, Image::GRAVITY_SOUTHWEST, Image::GRAVITY_SOUTH, Image::GRAVITY_SOUTHEAST]), 'Image crop gravity', true) ->param('quality', 100, new Range(0, 100), 'Preview image quality. Pass an integer between 0 to 100. Defaults to 100.', true) ->param('borderWidth', 0, new Range(0, 100), 'Preview image border in pixels. Pass an integer between 0 to 100. Defaults to 0.', true) ->param('borderColor', '', new HexColor(), 'Preview image border color. Use a valid HEX color, no # is needed for prefix.', true) From ffa06e7de45245a38dd37f542192fd0fa7981b37 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Wed, 9 Jun 2021 15:13:12 +0545 Subject: [PATCH 21/52] abuse audit setup for ocnsole db --- app/http.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/http.php b/app/http.php index 0856a69f9a..b4f70e190f 100644 --- a/app/http.php +++ b/app/http.php @@ -14,6 +14,8 @@ use Utopia\App; use Utopia\CLI\Console; use Utopia\Config\Config; use Utopia\Database\Validator\Authorization as Authorization2; +use Utopia\Audit\Audit; +use Utopia\Abuse\Adapters\TimeLimit; // xdebug_start_trace('/tmp/trace'); @@ -67,6 +69,12 @@ $http->on('start', function (Server $http) use ($payloadSize, $register) { $register->get('cache')->flushAll(); $dbForConsole->create(); + + $audit = new Audit($dbForConsole); + $audit->setup(); + + $adapter = new TimeLimit("", 0, 1, $dbForConsole); + $adapter->setup(); foreach ($collections as $key => $collection) { $dbForConsole->createCollection($key); From 0a5646482f22ea3d1084ef918dd80db09f42ac25 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Wed, 9 Jun 2021 15:13:24 +0545 Subject: [PATCH 22/52] abuse-audit update --- composer.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/composer.lock b/composer.lock index cc7393c775..5d0f64368b 100644 --- a/composer.lock +++ b/composer.lock @@ -1607,7 +1607,7 @@ "source": { "type": "git", "url": "https://github.com/lohanidamodar/abuse", - "reference": "525cdf6674e11a7229cba31941915ac50a3e575a" + "reference": "4f3349b3c1c85353708d13f7eb3c34c0762d4828" }, "require": { "ext-pdo": "*", @@ -1641,7 +1641,7 @@ "upf", "utopia" ], - "time": "2021-06-04T08:40:29+00:00" + "time": "2021-06-09T09:11:16+00:00" }, { "name": "utopia-php/analytics", @@ -1704,7 +1704,7 @@ "source": { "type": "git", "url": "https://github.com/lohanidamodar/audit", - "reference": "3421b6a54740fbb29570004848cdc0b818717e83" + "reference": "b3b85524717bbf52cfe71f01474c4012bfd4323a" }, "require": { "ext-pdo": "*", @@ -1738,7 +1738,7 @@ "upf", "utopia" ], - "time": "2021-06-04T08:41:19+00:00" + "time": "2021-06-09T09:10:27+00:00" }, { "name": "utopia-php/cache", From 25888151fb029ff4fe0669c033d8ac71a2ecd3d2 Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Wed, 9 Jun 2021 18:15:38 +0530 Subject: [PATCH 23/52] feat: add android platform config --- app/config/platforms.php | 19 ++++++++++--------- app/tasks/sdks.php | 12 +++++------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/app/config/platforms.php b/app/config/platforms.php index cfb21f3d82..40da048b23 100644 --- a/app/config/platforms.php +++ b/app/config/platforms.php @@ -109,19 +109,20 @@ return [ 'gitUserName' => 'appwrite', ], [ - 'key' => 'kotlin', - 'name' => 'Kotlin', - 'url' => '', - 'package' => '', - 'enabled' => false, - 'beta' => false, + 'key' => 'android', + 'name' => 'Android', + 'version' => '0.0.0-SNAPSHOT', + 'url' => 'https://github.com/appwrite/sdk-for-android', + 'package' => 'https://pub.dev/packages/appwrite', + 'enabled' => true, + 'beta' => true, 'dev' => false, 'hidden' => false, 'family' => APP_PLATFORM_CLIENT, 'prism' => 'kotlin', - 'source' => false, - 'gitUrl' => 'git@github.com:appwrite/sdk-for-kotlin.git', - 'gitRepoName' => 'sdk-for-kotlin', + 'source' => \realpath(__DIR__ . '/../sdks/client-android'), + 'gitUrl' => 'git@github.com:appwrite/sdk-for-android.git', + 'gitRepoName' => 'sdk-for-android', 'gitUserName' => 'appwrite', ], // [ diff --git a/app/tasks/sdks.php b/app/tasks/sdks.php index 108ce7a486..adc50392cf 100644 --- a/app/tasks/sdks.php +++ b/app/tasks/sdks.php @@ -15,7 +15,7 @@ use Appwrite\SDK\Language\Deno; use Appwrite\SDK\Language\DotNet; use Appwrite\SDK\Language\Flutter; use Appwrite\SDK\Language\Go; -use Appwrite\SDK\Language\Java; +use Appwrite\SDK\Language\Kotlin; use Appwrite\SDK\Language\Swift; $cli @@ -134,9 +134,6 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND case 'go': $config = new Go(); break; - case 'java': - $config = new Java(); - break; case 'swift': $config = new Swift(); break; @@ -144,6 +141,9 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND $cover = ''; $config = new DotNet(); break; + case 'android': + $config = new Kotlin(); + break; default: throw new Exception('Language "'.$language['key'].'" not supported'); break; @@ -155,9 +155,7 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND $sdk ->setName($language['name']) - ->setDescription("Appwrite is an open-source backend as a service server that abstract and simplify complex and repetitive development tasks behind a very simple to use REST API. Appwrite aims to help you develop your apps faster and in a more secure way. - Use the {$language['name']} SDK to integrate your app with the Appwrite server to easily start interacting with all of Appwrite backend APIs and tools. - For full API documentation and tutorials go to [https://appwrite.io/docs](https://appwrite.io/docs)") + ->setDescription("Appwrite is an open-source backend as a service server that abstract and simplify complex and repetitive development tasks behind a very simple to use REST API. Appwrite aims to help you develop your apps faster and in a more secure way. Use the {$language['name']} SDK to integrate your app with the Appwrite server to easily start interacting with all of Appwrite backend APIs and tools. For full API documentation and tutorials go to [https://appwrite.io/docs](https://appwrite.io/docs)") ->setShortDescription('Appwrite is an open-source self-hosted backend server that abstract and simplify complex and repetitive development tasks behind a very simple REST API') ->setLicense($license) ->setLicenseContent($licenseContent) From 0cbbcd38295d4eedbefbba27b1793a17f2f1e6d8 Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Wed, 9 Jun 2021 18:31:35 +0530 Subject: [PATCH 24/52] feat: update package link --- app/config/platforms.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/config/platforms.php b/app/config/platforms.php index 40da048b23..1409cf2cc9 100644 --- a/app/config/platforms.php +++ b/app/config/platforms.php @@ -113,7 +113,7 @@ return [ 'name' => 'Android', 'version' => '0.0.0-SNAPSHOT', 'url' => 'https://github.com/appwrite/sdk-for-android', - 'package' => 'https://pub.dev/packages/appwrite', + 'package' => 'https://repo1.maven.org/maven2/io/appwrite/sdk-for-android/', 'enabled' => true, 'beta' => true, 'dev' => false, From 3425fe351a81740fe9c89dfe1f2dd6f7bc33253b Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Wed, 9 Jun 2021 18:59:31 +0530 Subject: [PATCH 25/52] feat: fixed some issues with the guide --- docs/sdks/android/GETTING_STARTED.md | 16 ++++++++-------- docs/sdks/flutter/GETTING_STARTED.md | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/sdks/android/GETTING_STARTED.md b/docs/sdks/android/GETTING_STARTED.md index 23223d85de..8a38e86fe0 100644 --- a/docs/sdks/android/GETTING_STARTED.md +++ b/docs/sdks/android/GETTING_STARTED.md @@ -1,14 +1,14 @@ ## Getting Started ### Add your Android Platform -To init your SDK and start interacting with Appwrite services, you need to add a new Flutter platform to your project. To add a new platform, go to your Appwrite console, choose the project you created in the step before, and click the 'Add Platform' button. +To initialize your SDK and start interacting with Appwrite services, you need to add a new Android platform to your project. To add a new platform, go to your Appwrite console, choose the project you created in the step before, and click the 'Add Platform' button. -From the options, choose to add a new **Flutter** platform and add your app credentials, ignoring iOS. +From the options, choose to add a new **Android** platform and add your app credentials. -Add your app name and package name, Your package name is generally the applicationId in your app-level build.gradle file. By registering your new app platform, you are allowing your app to communicate with the Appwrite API. +Add your app name and package name. Your package name is generally the applicationId in your app-level `build.gradle` file. By registering a new platform, you are allowing your app to communicate with the Appwrite API. ### OAuth -In order to capture the Appwrite OAuth callback url, the following activity needs to be added to your [AndroidManifest.xml](https://github.com/appwrite/playground-for-flutter/blob/master/android/app/src/main/AndroidManifest.xml). Be sure to relpace the **[PROJECT_ID]** string with your actual Appwrite project ID. You can find your Appwrite project ID in you project settings screen in your Appwrite console. +In order to capture the Appwrite OAuth callback url, the following activity needs to be added to your [AndroidManifest.xml](). Be sure to replace the **[PROJECT_ID]** string with your actual Appwrite project ID. You can find your Appwrite project ID in your project settings screen in the console. ```xml @@ -41,7 +41,7 @@ val client = Client(context) Before starting to send any API calls to your new Appwrite instance, make sure your Android emulators has network access to the Appwrite server hostname or IP address. -When trying to connect to Appwrite from an emulator or a mobile device, localhost is the hostname for the device or emulator and not your local Appwrite instance. You should replace localhost with your private IP as the Appwrite endpoint's hostname. You can also use a service like [ngrok](https://ngrok.com/) to proxy the Appwrite API. +When trying to connect to Appwrite from an emulator or a mobile device, localhost is the hostname of the device or emulator and not your local Appwrite instance. You should replace localhost with your private IP. You can also use a service like [ngrok](https://ngrok.com/) to proxy the Appwrite API. ### Make Your First Request @@ -75,7 +75,7 @@ val response = account.create( ``` ### Error Handling -The Apopwrite Android SDK raises `AppwriteException` object with `message`, `code` and `response` properties. You can handle any errors by catching `AppwriteException` and present the `message` to the user or handle it yourself based on provided error information. Below is an example. +The Appwrite Android SDK raises an `AppwriteException` object with `message`, `code` and `response` properties. You can handle any errors by catching `AppwriteException` and present the `message` to the user or handle it yourself based on the provided error information. Below is an example. ```kotlin try { @@ -87,8 +87,8 @@ try { ``` ### Learn more -You can use followng resources to learn more and get help +You can use following resources to learn more and get help - 🚀 [Getting Started Tutorial](https://appwrite.io/docs/getting-started-for-android) - 📜 [Appwrite Docs](https://appwrite.io/docs) - 💬 [Discord Community](https://appwrite.io/discord) -- - 🚂 [Appwrite Android Playground](https://github.com/appwrite/playground-for-android) \ No newline at end of file +- 🚂 [Appwrite Android Playground](https://github.com/appwrite/playground-for-android) \ No newline at end of file diff --git a/docs/sdks/flutter/GETTING_STARTED.md b/docs/sdks/flutter/GETTING_STARTED.md index 9af9720d89..bf2d613c74 100644 --- a/docs/sdks/flutter/GETTING_STARTED.md +++ b/docs/sdks/flutter/GETTING_STARTED.md @@ -23,7 +23,7 @@ The Appwrite SDK uses ASWebAuthenticationSession on iOS 12+ and SFAuthentication 4. In Deployment Info, 'Target' select iOS 11.0 ### Android -In order to capture the Appwrite OAuth callback url, the following activity needs to be added to your [AndroidManifest.xml](https://github.com/appwrite/playground-for-flutter/blob/master/android/app/src/main/AndroidManifest.xml). Be sure to relpace the **[PROJECT_ID]** string with your actual Appwrite project ID. You can find your Appwrite project ID in you project settings screen in your Appwrite console. +In order to capture the Appwrite OAuth callback url, the following activity needs to be added to your [AndroidManifest.xml](https://github.com/appwrite/playground-for-flutter/blob/master/android/app/src/main/AndroidManifest.xml). Be sure to replace the **[PROJECT_ID]** string with your actual Appwrite project ID. You can find your Appwrite project ID in your project settings screen in the console. ```xml From 0492ccadd58dbfd3db339832a57a5db55335ccb7 Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Wed, 9 Jun 2021 20:56:08 +0530 Subject: [PATCH 26/52] feat: update getting started guides --- docs/sdks/android/GETTING_STARTED.md | 10 +++++----- docs/sdks/deno/GETTING_STARTED.md | 4 ++-- docs/sdks/flutter/GETTING_STARTED.md | 4 ++-- docs/sdks/nodejs/GETTING_STARTED.md | 4 ++-- docs/sdks/php/GETTING_STARTED.md | 4 ++-- docs/sdks/python/GETTING_STARTED.md | 4 ++-- docs/sdks/ruby/GETTING_STARTED.md | 4 ++-- docs/sdks/web/GETTING_STARTED.md | 4 ++-- 8 files changed, 19 insertions(+), 19 deletions(-) diff --git a/docs/sdks/android/GETTING_STARTED.md b/docs/sdks/android/GETTING_STARTED.md index 8a38e86fe0..c0bba25051 100644 --- a/docs/sdks/android/GETTING_STARTED.md +++ b/docs/sdks/android/GETTING_STARTED.md @@ -1,14 +1,14 @@ ## Getting Started ### Add your Android Platform -To initialize your SDK and start interacting with Appwrite services, you need to add a new Android platform to your project. To add a new platform, go to your Appwrite console, choose the project you created in the step before, and click the 'Add Platform' button. +To initialize your SDK and start interacting with Appwrite services, you need to add a new Android platform to your project. To add a new platform, go to your Appwrite console, select your project (create one if you haven't already), and click the 'Add Platform' button on the project Dashboard. From the options, choose to add a new **Android** platform and add your app credentials. Add your app name and package name. Your package name is generally the applicationId in your app-level `build.gradle` file. By registering a new platform, you are allowing your app to communicate with the Appwrite API. -### OAuth -In order to capture the Appwrite OAuth callback url, the following activity needs to be added to your [AndroidManifest.xml](). Be sure to replace the **[PROJECT_ID]** string with your actual Appwrite project ID. You can find your Appwrite project ID in your project settings screen in the console. +### Registering additional activities +In order to capture the Appwrite OAuth callback url, the following activity needs to be added to your [AndroidManifest.xml](https://github.com/appwrite/playground-for-android/blob/master/app/src/main/AndroidManifest.xml). Be sure to replace the **[PROJECT_ID]** string with your actual Appwrite project ID. You can find your Appwrite project ID in your project settings screen in the console. ```xml @@ -27,7 +27,7 @@ In order to capture the Appwrite OAuth callback url, the following activity need ### Init your SDK -

    Initialize your SDK code with your project ID, which can be found in your project settings page. +

    Initialize your SDK with your Appwrite server API endpoint and project ID, which can be found in your project settings page. ```kotlin import io.appwrite.Client @@ -45,7 +45,7 @@ When trying to connect to Appwrite from an emulator or a mobile device, localhos ### Make Your First Request -

    Once your SDK object is set, access any of the Appwrite services and choose any request to send. Full documentation for any service method you would like to use can be found in your SDK documentation or in the API References section. +

    Once your SDK object is set, access any of the Appwrite services and choose any request to send. Full documentation for any service method you would like to use can be found in your SDK documentation or in the [API References](https://appwrite.io/docs) section. ```kotlin // Register User diff --git a/docs/sdks/deno/GETTING_STARTED.md b/docs/sdks/deno/GETTING_STARTED.md index b8f851a4b9..3a239003fa 100644 --- a/docs/sdks/deno/GETTING_STARTED.md +++ b/docs/sdks/deno/GETTING_STARTED.md @@ -1,7 +1,7 @@ ## Getting Started ### Init your SDK -Initialize your SDK code with your project ID which can be found in your project settings page and your new API secret Key from project's API keys section. +Initialize your SDK with your Appwrite server API endpoint and project ID which can be found in your project settings page and your new API secret Key from project's API keys section. ```typescript let client = new sdk.Client(); @@ -17,7 +17,7 @@ client ### Make your first request -Once your SDK object is set, create any of the Appwrite service objects and choose any request to send. Full documentation for any service method you would like to use can be found in your SDK documentation or in the API References section. +Once your SDK object is set, create any of the Appwrite service objects and choose any request to send. Full documentation for any service method you would like to use can be found in your SDK documentation or in the [API References](https://appwrite.io/docs) section. ```typescript let users = new sdk.Users(client); diff --git a/docs/sdks/flutter/GETTING_STARTED.md b/docs/sdks/flutter/GETTING_STARTED.md index bf2d613c74..0a7244b197 100644 --- a/docs/sdks/flutter/GETTING_STARTED.md +++ b/docs/sdks/flutter/GETTING_STARTED.md @@ -48,7 +48,7 @@ While running Flutter Web, make sure your Appwrite server and your Flutter clien ### Init your SDK -

    Initialize your SDK code with your project ID, which can be found in your project settings page. +

    Initialize your SDK with your Appwrite server API endpoint and project ID, which can be found in your project settings page. ```dart import 'package:appwrite/appwrite.dart'; @@ -68,7 +68,7 @@ When trying to connect to Appwrite from an emulator or a mobile device, localhos ### Make Your First Request -

    Once your SDK object is set, access any of the Appwrite services and choose any request to send. Full documentation for any service method you would like to use can be found in your SDK documentation or in the API References section. +

    Once your SDK object is set, access any of the Appwrite services and choose any request to send. Full documentation for any service method you would like to use can be found in your SDK documentation or in the [API References](https://appwrite.io/docs) section. ```dart // Register User diff --git a/docs/sdks/nodejs/GETTING_STARTED.md b/docs/sdks/nodejs/GETTING_STARTED.md index b2ce5f091c..ee19ca3c3d 100644 --- a/docs/sdks/nodejs/GETTING_STARTED.md +++ b/docs/sdks/nodejs/GETTING_STARTED.md @@ -1,7 +1,7 @@ ## Getting Started ### Init your SDK -Initialize your SDK code with your project ID which can be found in your project settings page and your new API secret Key project API keys section. +Initialize your SDK with your Appwrite server API endpoint and project ID which can be found in your project settings page and your new API secret Key project API keys section. ```js const sdk = require('node-appwrite'); @@ -17,7 +17,7 @@ client ``` ### Make Your First Request -Once your SDK object is set, create any of the Appwrite service objects and choose any request to send. Full documentation for any service method you would like to use can be found in your SDK documentation or in the API References section. +Once your SDK object is set, create any of the Appwrite service objects and choose any request to send. Full documentation for any service method you would like to use can be found in your SDK documentation or in the [API References](https://appwrite.io/docs) section. ```js let users = new sdk.Users(client); diff --git a/docs/sdks/php/GETTING_STARTED.md b/docs/sdks/php/GETTING_STARTED.md index a7ceb61372..7aef8cdccf 100644 --- a/docs/sdks/php/GETTING_STARTED.md +++ b/docs/sdks/php/GETTING_STARTED.md @@ -1,7 +1,7 @@ ## Getting Started ### Init your SDK -Initialize your SDK code with your project ID which can be found in your project settings page and your new API secret Key from project's API keys section. +Initialize your SDK with your Appwrite server API endpoint and project ID which can be found in your project settings page and your new API secret Key from project's API keys section. ```php $client = new Client(); @@ -15,7 +15,7 @@ $client ``` ### Make Your First Request -Once your SDK object is set, create any of the Appwrite service objects and choose any request to send. Full documentation for any service method you would like to use can be found in your SDK documentation or in the API References section. +Once your SDK object is set, create any of the Appwrite service objects and choose any request to send. Full documentation for any service method you would like to use can be found in your SDK documentation or in the [API References](https://appwrite.io/docs) section. ```php $users = new Users($client); diff --git a/docs/sdks/python/GETTING_STARTED.md b/docs/sdks/python/GETTING_STARTED.md index 81c7954509..b68f51affb 100644 --- a/docs/sdks/python/GETTING_STARTED.md +++ b/docs/sdks/python/GETTING_STARTED.md @@ -1,7 +1,7 @@ ## Getting Started ### Init your SDK -Initialize your SDK code with your project ID which can be found in your project settings page and your new API secret Key from project's API keys section. +Initialize your SDK with your Appwrite server API endpoint and project ID which can be found in your project settings page and your new API secret Key from project's API keys section. ```python from appwrite.client import Client @@ -18,7 +18,7 @@ client = Client() ``` ### Make Your First Request -Once your SDK object is set, create any of the Appwrite service objects and choose any request to send. Full documentation for any service method you would like to use can be found in your SDK documentation or in the API References section. +Once your SDK object is set, create any of the Appwrite service objects and choose any request to send. Full documentation for any service method you would like to use can be found in your SDK documentation or in the [API References](https://appwrite.io/docs) section. ```python users = Users(client) diff --git a/docs/sdks/ruby/GETTING_STARTED.md b/docs/sdks/ruby/GETTING_STARTED.md index acabf1c0a9..820f1669eb 100644 --- a/docs/sdks/ruby/GETTING_STARTED.md +++ b/docs/sdks/ruby/GETTING_STARTED.md @@ -1,7 +1,7 @@ ## Getting Started ### Init your SDK -Initialize your SDK code with your project ID which can be found in your project settings page and your new API secret Key from project's API keys section. +Initialize your SDK with your Appwrite server API endpoint and project ID which can be found in your project settings page and your new API secret Key from project's API keys section. ```ruby require 'appwrite' @@ -17,7 +17,7 @@ client ``` ### Make Your First Request -Once your SDK object is set, create any of the Appwrite service objects and choose any request to send. Full documentation for any service method you would like to use can be found in your SDK documentation or in the API References section. +Once your SDK object is set, create any of the Appwrite service objects and choose any request to send. Full documentation for any service method you would like to use can be found in your SDK documentation or in the [API References](https://appwrite.io/docs) section. ```ruby users = Appwrite::Users.new(client); diff --git a/docs/sdks/web/GETTING_STARTED.md b/docs/sdks/web/GETTING_STARTED.md index ba337b178f..1fe6250d29 100644 --- a/docs/sdks/web/GETTING_STARTED.md +++ b/docs/sdks/web/GETTING_STARTED.md @@ -6,7 +6,7 @@ For you to init your SDK and interact with Appwrite services you need to add a w From the options, choose to add a **Web** platform and add your client app hostname. By adding your hostname to your project platform you are allowing cross-domain communication between your project and the Appwrite API. ### Init your SDK -Initialize your SDK code with your project ID which can be found in your project settings page. +Initialize your SDK with your Appwrite server API endpoint and project ID which can be found in your project settings page. ```js // Init your Web SDK @@ -19,7 +19,7 @@ sdk ``` ### Make Your First Request -Once your SDK object is set, access any of the Appwrite services and choose any request to send. Full documentation for any service method you would like to use can be found in your SDK documentation or in the API References section. +Once your SDK object is set, access any of the Appwrite services and choose any request to send. Full documentation for any service method you would like to use can be found in your SDK documentation or in the [API References](https://appwrite.io/docs) section. ```js // Register User From bdb4e98123b114f1bc39c15e137ef68a18c5ee44 Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Wed, 9 Jun 2021 21:00:52 +0530 Subject: [PATCH 27/52] feat: added namespace --- app/tasks/sdks.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/tasks/sdks.php b/app/tasks/sdks.php index adc50392cf..96f8742296 100644 --- a/app/tasks/sdks.php +++ b/app/tasks/sdks.php @@ -155,6 +155,7 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND $sdk ->setName($language['name']) + ->setNamespace('io appwrite') ->setDescription("Appwrite is an open-source backend as a service server that abstract and simplify complex and repetitive development tasks behind a very simple to use REST API. Appwrite aims to help you develop your apps faster and in a more secure way. Use the {$language['name']} SDK to integrate your app with the Appwrite server to easily start interacting with all of Appwrite backend APIs and tools. For full API documentation and tutorials go to [https://appwrite.io/docs](https://appwrite.io/docs)") ->setShortDescription('Appwrite is an open-source self-hosted backend server that abstract and simplify complex and repetitive development tasks behind a very simple REST API') ->setLicense($license) From 1f1b68f0cada5692926fae6c3c2bf6c9a26c72ef Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Thu, 10 Jun 2021 15:16:03 +0545 Subject: [PATCH 28/52] update dependency --- composer.lock | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/composer.lock b/composer.lock index 053de4bab1..9e7affe29c 100644 --- a/composer.lock +++ b/composer.lock @@ -1742,16 +1742,16 @@ }, { "name": "utopia-php/image", - "version": "0.3.1", + "version": "0.3.2", "source": { "type": "git", "url": "https://github.com/utopia-php/image.git", - "reference": "20849a3a55790bd6eb3decde9f9708f04d58e489" + "reference": "2044fdd44d87c4253cfe929cca975fd037461b00" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/image/zipball/20849a3a55790bd6eb3decde9f9708f04d58e489", - "reference": "20849a3a55790bd6eb3decde9f9708f04d58e489", + "url": "https://api.github.com/repos/utopia-php/image/zipball/2044fdd44d87c4253cfe929cca975fd037461b00", + "reference": "2044fdd44d87c4253cfe929cca975fd037461b00", "shasum": "" }, "require": { @@ -1789,9 +1789,9 @@ ], "support": { "issues": "https://github.com/utopia-php/image/issues", - "source": "https://github.com/utopia-php/image/tree/0.3.1" + "source": "https://github.com/utopia-php/image/tree/0.3.2" }, - "time": "2021-06-09T07:12:35+00:00" + "time": "2021-06-10T09:16:11+00:00" }, { "name": "utopia-php/locale", @@ -6025,5 +6025,5 @@ "platform-overrides": { "php": "8.0" }, - "plugin-api-version": "2.0.0" + "plugin-api-version": "2.1.0" } From da1ecfaffefd184aadc8c2a70ba08e1ccb88c1a3 Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Thu, 10 Jun 2021 15:05:16 +0530 Subject: [PATCH 29/52] feat: added android origin check --- src/Appwrite/Network/Validator/Origin.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Appwrite/Network/Validator/Origin.php b/src/Appwrite/Network/Validator/Origin.php index 8101d9a30c..aa9294f65c 100644 --- a/src/Appwrite/Network/Validator/Origin.php +++ b/src/Appwrite/Network/Validator/Origin.php @@ -13,6 +13,8 @@ class Origin extends Validator const CLIENT_TYPE_FLUTTER_MACOS = 'flutter-macos'; const CLIENT_TYPE_FLUTTER_WINDOWS = 'flutter-windows'; const CLIENT_TYPE_FLUTTER_LINUX = 'flutter-linux'; + const CLIENT_TYPE_ANDROID = 'android'; + const SCHEME_TYPE_HTTP = 'http'; const SCHEME_TYPE_HTTPS = 'https'; @@ -69,6 +71,7 @@ class Origin extends Validator case self::CLIENT_TYPE_FLUTTER_MACOS: case self::CLIENT_TYPE_FLUTTER_WINDOWS: case self::CLIENT_TYPE_FLUTTER_LINUX: + case self::CLIENT_TYPE_ANDROID: $this->clients[] = (isset($platform['key'])) ? $platform['key'] : ''; break; @@ -90,7 +93,7 @@ class Origin extends Validator } /** - * Check if Origin has been whiltlisted + * Check if Origin has been whitelisted * for access to the API * * @param mixed $origin From c3d4897d36215d82ce8860a1363d30f69c344d37 Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Thu, 10 Jun 2021 15:13:32 +0530 Subject: [PATCH 30/52] feat: changed terminology --- src/Appwrite/Network/Validator/Origin.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Appwrite/Network/Validator/Origin.php b/src/Appwrite/Network/Validator/Origin.php index aa9294f65c..38e37e60b6 100644 --- a/src/Appwrite/Network/Validator/Origin.php +++ b/src/Appwrite/Network/Validator/Origin.php @@ -93,7 +93,7 @@ class Origin extends Validator } /** - * Check if Origin has been whitelisted + * Check if Origin has been allowed * for access to the API * * @param mixed $origin From c981ff39c0ca5e2a450d4a2d1060cf1f2f1be7d3 Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Thu, 10 Jun 2021 17:04:14 +0530 Subject: [PATCH 31/52] feat: added error models to response model --- src/Appwrite/Utopia/Response/Filters/V07.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Appwrite/Utopia/Response/Filters/V07.php b/src/Appwrite/Utopia/Response/Filters/V07.php index 6001450b9a..36831b4733 100644 --- a/src/Appwrite/Utopia/Response/Filters/V07.php +++ b/src/Appwrite/Utopia/Response/Filters/V07.php @@ -55,6 +55,8 @@ class V07 extends Filter { case Response::MODEL_ANY: case Response::MODEL_PREFERENCES: /** ANY was replaced by PREFERENCES in 0.8.x but this is backward compatible with 0.7.x */ case Response::MODEL_NONE: + case Response::MODEL_ERROR: + case Response::MODEL_ERROR_DEV: $parsedResponse = $content; break; default: From 43d9c416a352e547911e96d7966b08570742ceb8 Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Fri, 11 Jun 2021 12:44:30 +0530 Subject: [PATCH 32/52] feat: added iOS platform to origin validator --- .../.github/workflows/publish.yml | 53 ++ app/sdks/client-android/.gitignore | 12 + app/sdks/client-android/CHANGELOG.md | 1 + app/sdks/client-android/LICENSE.md | 12 + app/sdks/client-android/README.md | 157 +++++ app/sdks/client-android/build.gradle | 36 + .../account/create-anonymous-session.md | 10 + .../docs/examples/account/create-j-w-t.md | 10 + .../examples/account/create-o-auth2session.md | 10 + .../docs/examples/account/create-recovery.md | 10 + .../docs/examples/account/create-session.md | 10 + .../examples/account/create-verification.md | 10 + .../docs/examples/account/create.md | 10 + .../docs/examples/account/delete-session.md | 10 + .../docs/examples/account/delete-sessions.md | 10 + .../docs/examples/account/delete.md | 10 + .../docs/examples/account/get-logs.md | 10 + .../docs/examples/account/get-prefs.md | 10 + .../docs/examples/account/get-sessions.md | 10 + .../docs/examples/account/get.md | 10 + .../docs/examples/account/update-email.md | 10 + .../docs/examples/account/update-name.md | 10 + .../docs/examples/account/update-password.md | 10 + .../docs/examples/account/update-prefs.md | 10 + .../docs/examples/account/update-recovery.md | 10 + .../examples/account/update-verification.md | 10 + .../docs/examples/avatars/get-browser.md | 10 + .../docs/examples/avatars/get-credit-card.md | 10 + .../docs/examples/avatars/get-favicon.md | 10 + .../docs/examples/avatars/get-flag.md | 10 + .../docs/examples/avatars/get-image.md | 10 + .../docs/examples/avatars/get-initials.md | 10 + .../docs/examples/avatars/get-q-r.md | 10 + .../docs/examples/database/create-document.md | 10 + .../docs/examples/database/delete-document.md | 10 + .../docs/examples/database/get-document.md | 10 + .../docs/examples/database/list-documents.md | 10 + .../docs/examples/database/update-document.md | 10 + .../examples/functions/create-execution.md | 10 + .../docs/examples/functions/get-execution.md | 10 + .../examples/functions/list-executions.md | 10 + .../docs/examples/locale/get-continents.md | 10 + .../docs/examples/locale/get-countries-e-u.md | 10 + .../examples/locale/get-countries-phones.md | 10 + .../docs/examples/locale/get-countries.md | 10 + .../docs/examples/locale/get-currencies.md | 10 + .../docs/examples/locale/get-languages.md | 10 + .../docs/examples/locale/get.md | 10 + .../docs/examples/storage/create-file.md | 10 + .../docs/examples/storage/delete-file.md | 10 + .../examples/storage/get-file-download.md | 10 + .../docs/examples/storage/get-file-preview.md | 10 + .../docs/examples/storage/get-file-view.md | 10 + .../docs/examples/storage/get-file.md | 10 + .../docs/examples/storage/list-files.md | 10 + .../docs/examples/storage/update-file.md | 10 + .../docs/examples/teams/create-membership.md | 10 + .../docs/examples/teams/create.md | 10 + .../docs/examples/teams/delete-membership.md | 10 + .../docs/examples/teams/delete.md | 10 + .../docs/examples/teams/get-memberships.md | 10 + .../client-android/docs/examples/teams/get.md | 10 + .../docs/examples/teams/list.md | 10 + .../examples/teams/update-membership-roles.md | 10 + .../teams/update-membership-status.md | 10 + .../docs/examples/teams/update.md | 10 + app/sdks/client-android/example/.gitignore | 1 + app/sdks/client-android/example/build.gradle | 59 ++ .../example/src/main/AndroidManifest.xml | 20 + .../java/io/appwrite/android/MainActivity.kt | 23 + .../android/ui/accounts/AccountsFragment.kt | 69 ++ .../android/ui/accounts/AccountsViewModel.kt | 96 +++ .../java/io/appwrite/android/utils/Client.kt | 20 + .../java/io/appwrite/android/utils/Event.kt | 27 + .../res/drawable/ic_launcher_background.xml | 170 +++++ .../res/drawable/ic_launcher_foreground.xml | 30 + .../src/main/res/layout/activity_main.xml | 15 + .../src/main/res/layout/fragment_account.xml | 124 ++++ .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 + .../example/src/main/res/values/colors.xml | 10 + .../example/src/main/res/values/strings.xml | 3 + .../example/src/main/res/values/themes.xml | 16 + app/sdks/client-android/gradle.properties | 19 + .../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 54329 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 + app/sdks/client-android/gradlew | 172 +++++ app/sdks/client-android/gradlew.bat | 84 +++ app/sdks/client-android/library/.gitignore | 1 + app/sdks/client-android/library/build.gradle | 77 +++ .../client-android/library/example/README.md | 0 .../library/src/main/AndroidManifest.xml | 8 + .../src/main/java/io/appwrite/Client.kt | 259 ++++++++ .../main/java/io/appwrite/KeepAliveService.kt | 14 + .../main/java/io/appwrite/WebAuthComponent.kt | 100 +++ .../appwrite/exceptions/AppwriteException.kt | 9 + .../io/appwrite/extensions/JsonExtensions.kt | 12 + .../src/main/java/io/appwrite/models/Error.kt | 6 + .../main/java/io/appwrite/services/Account.kt | 626 ++++++++++++++++++ .../main/java/io/appwrite/services/Avatars.kt | 241 +++++++ .../java/io/appwrite/services/BaseService.kt | 5 + .../java/io/appwrite/services/Database.kt | 196 ++++++ .../java/io/appwrite/services/Functions.kt | 107 +++ .../main/java/io/appwrite/services/Locale.kt | 171 +++++ .../main/java/io/appwrite/services/Storage.kt | 264 ++++++++ .../main/java/io/appwrite/services/Teams.kt | 332 ++++++++++ .../io/appwrite/views/CallbackActivity.kt | 20 + .../scripts/publish-config.gradle | 37 ++ .../scripts/publish-module.gradle | 84 +++ app/sdks/client-android/settings.gradle | 3 + app/tasks/sdks.php | 4 +- composer.json | 2 +- composer.lock | 224 ++++--- .../account/create-anonymous-session.md | 10 + .../examples/account/create-j-w-t.md | 10 + .../examples/account/create-o-auth2session.md | 10 + .../examples/account/create-recovery.md | 10 + .../examples/account/create-session.md | 10 + .../examples/account/create-verification.md | 10 + .../client-android/examples/account/create.md | 10 + .../examples/account/delete-session.md | 10 + .../examples/account/delete-sessions.md | 10 + .../client-android/examples/account/delete.md | 10 + .../examples/account/get-logs.md | 10 + .../examples/account/get-prefs.md | 10 + .../examples/account/get-sessions.md | 10 + .../client-android/examples/account/get.md | 10 + .../examples/account/update-email.md | 10 + .../examples/account/update-name.md | 10 + .../examples/account/update-password.md | 10 + .../examples/account/update-prefs.md | 10 + .../examples/account/update-recovery.md | 10 + .../examples/account/update-verification.md | 10 + .../examples/avatars/get-browser.md | 10 + .../examples/avatars/get-credit-card.md | 10 + .../examples/avatars/get-favicon.md | 10 + .../examples/avatars/get-flag.md | 10 + .../examples/avatars/get-image.md | 10 + .../examples/avatars/get-initials.md | 10 + .../examples/avatars/get-q-r.md | 10 + .../examples/database/create-document.md | 10 + .../examples/database/delete-document.md | 10 + .../examples/database/get-document.md | 10 + .../examples/database/list-documents.md | 10 + .../examples/database/update-document.md | 10 + .../examples/functions/create-execution.md | 10 + .../examples/functions/get-execution.md | 10 + .../examples/functions/list-executions.md | 10 + .../examples/locale/get-continents.md | 10 + .../examples/locale/get-countries-e-u.md | 10 + .../examples/locale/get-countries-phones.md | 10 + .../examples/locale/get-countries.md | 10 + .../examples/locale/get-currencies.md | 10 + .../examples/locale/get-languages.md | 10 + .../client-android/examples/locale/get.md | 10 + .../examples/storage/create-file.md | 10 + .../examples/storage/delete-file.md | 10 + .../examples/storage/get-file-download.md | 10 + .../examples/storage/get-file-preview.md | 10 + .../examples/storage/get-file-view.md | 10 + .../examples/storage/get-file.md | 10 + .../examples/storage/list-files.md | 10 + .../examples/storage/update-file.md | 10 + .../examples/teams/create-membership.md | 10 + .../client-android/examples/teams/create.md | 10 + .../examples/teams/delete-membership.md | 10 + .../client-android/examples/teams/delete.md | 10 + .../examples/teams/get-memberships.md | 10 + .../client-android/examples/teams/get.md | 10 + .../client-android/examples/teams/list.md | 10 + .../examples/teams/update-membership-roles.md | 10 + .../teams/update-membership-status.md | 10 + .../client-android/examples/teams/update.md | 10 + src/Appwrite/Network/Validator/Origin.php | 2 + 174 files changed, 5169 insertions(+), 80 deletions(-) create mode 100644 app/sdks/client-android/.github/workflows/publish.yml create mode 100644 app/sdks/client-android/.gitignore create mode 100644 app/sdks/client-android/CHANGELOG.md create mode 100644 app/sdks/client-android/LICENSE.md create mode 100644 app/sdks/client-android/README.md create mode 100644 app/sdks/client-android/build.gradle create mode 100644 app/sdks/client-android/docs/examples/account/create-anonymous-session.md create mode 100644 app/sdks/client-android/docs/examples/account/create-j-w-t.md create mode 100644 app/sdks/client-android/docs/examples/account/create-o-auth2session.md create mode 100644 app/sdks/client-android/docs/examples/account/create-recovery.md create mode 100644 app/sdks/client-android/docs/examples/account/create-session.md create mode 100644 app/sdks/client-android/docs/examples/account/create-verification.md create mode 100644 app/sdks/client-android/docs/examples/account/create.md create mode 100644 app/sdks/client-android/docs/examples/account/delete-session.md create mode 100644 app/sdks/client-android/docs/examples/account/delete-sessions.md create mode 100644 app/sdks/client-android/docs/examples/account/delete.md create mode 100644 app/sdks/client-android/docs/examples/account/get-logs.md create mode 100644 app/sdks/client-android/docs/examples/account/get-prefs.md create mode 100644 app/sdks/client-android/docs/examples/account/get-sessions.md create mode 100644 app/sdks/client-android/docs/examples/account/get.md create mode 100644 app/sdks/client-android/docs/examples/account/update-email.md create mode 100644 app/sdks/client-android/docs/examples/account/update-name.md create mode 100644 app/sdks/client-android/docs/examples/account/update-password.md create mode 100644 app/sdks/client-android/docs/examples/account/update-prefs.md create mode 100644 app/sdks/client-android/docs/examples/account/update-recovery.md create mode 100644 app/sdks/client-android/docs/examples/account/update-verification.md create mode 100644 app/sdks/client-android/docs/examples/avatars/get-browser.md create mode 100644 app/sdks/client-android/docs/examples/avatars/get-credit-card.md create mode 100644 app/sdks/client-android/docs/examples/avatars/get-favicon.md create mode 100644 app/sdks/client-android/docs/examples/avatars/get-flag.md create mode 100644 app/sdks/client-android/docs/examples/avatars/get-image.md create mode 100644 app/sdks/client-android/docs/examples/avatars/get-initials.md create mode 100644 app/sdks/client-android/docs/examples/avatars/get-q-r.md create mode 100644 app/sdks/client-android/docs/examples/database/create-document.md create mode 100644 app/sdks/client-android/docs/examples/database/delete-document.md create mode 100644 app/sdks/client-android/docs/examples/database/get-document.md create mode 100644 app/sdks/client-android/docs/examples/database/list-documents.md create mode 100644 app/sdks/client-android/docs/examples/database/update-document.md create mode 100644 app/sdks/client-android/docs/examples/functions/create-execution.md create mode 100644 app/sdks/client-android/docs/examples/functions/get-execution.md create mode 100644 app/sdks/client-android/docs/examples/functions/list-executions.md create mode 100644 app/sdks/client-android/docs/examples/locale/get-continents.md create mode 100644 app/sdks/client-android/docs/examples/locale/get-countries-e-u.md create mode 100644 app/sdks/client-android/docs/examples/locale/get-countries-phones.md create mode 100644 app/sdks/client-android/docs/examples/locale/get-countries.md create mode 100644 app/sdks/client-android/docs/examples/locale/get-currencies.md create mode 100644 app/sdks/client-android/docs/examples/locale/get-languages.md create mode 100644 app/sdks/client-android/docs/examples/locale/get.md create mode 100644 app/sdks/client-android/docs/examples/storage/create-file.md create mode 100644 app/sdks/client-android/docs/examples/storage/delete-file.md create mode 100644 app/sdks/client-android/docs/examples/storage/get-file-download.md create mode 100644 app/sdks/client-android/docs/examples/storage/get-file-preview.md create mode 100644 app/sdks/client-android/docs/examples/storage/get-file-view.md create mode 100644 app/sdks/client-android/docs/examples/storage/get-file.md create mode 100644 app/sdks/client-android/docs/examples/storage/list-files.md create mode 100644 app/sdks/client-android/docs/examples/storage/update-file.md create mode 100644 app/sdks/client-android/docs/examples/teams/create-membership.md create mode 100644 app/sdks/client-android/docs/examples/teams/create.md create mode 100644 app/sdks/client-android/docs/examples/teams/delete-membership.md create mode 100644 app/sdks/client-android/docs/examples/teams/delete.md create mode 100644 app/sdks/client-android/docs/examples/teams/get-memberships.md create mode 100644 app/sdks/client-android/docs/examples/teams/get.md create mode 100644 app/sdks/client-android/docs/examples/teams/list.md create mode 100644 app/sdks/client-android/docs/examples/teams/update-membership-roles.md create mode 100644 app/sdks/client-android/docs/examples/teams/update-membership-status.md create mode 100644 app/sdks/client-android/docs/examples/teams/update.md create mode 100644 app/sdks/client-android/example/.gitignore create mode 100644 app/sdks/client-android/example/build.gradle create mode 100644 app/sdks/client-android/example/src/main/AndroidManifest.xml create mode 100644 app/sdks/client-android/example/src/main/java/io/appwrite/android/MainActivity.kt create mode 100644 app/sdks/client-android/example/src/main/java/io/appwrite/android/ui/accounts/AccountsFragment.kt create mode 100644 app/sdks/client-android/example/src/main/java/io/appwrite/android/ui/accounts/AccountsViewModel.kt create mode 100644 app/sdks/client-android/example/src/main/java/io/appwrite/android/utils/Client.kt create mode 100644 app/sdks/client-android/example/src/main/java/io/appwrite/android/utils/Event.kt create mode 100644 app/sdks/client-android/example/src/main/res/drawable/ic_launcher_background.xml create mode 100644 app/sdks/client-android/example/src/main/res/drawable/ic_launcher_foreground.xml create mode 100644 app/sdks/client-android/example/src/main/res/layout/activity_main.xml create mode 100644 app/sdks/client-android/example/src/main/res/layout/fragment_account.xml create mode 100644 app/sdks/client-android/example/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 app/sdks/client-android/example/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 app/sdks/client-android/example/src/main/res/values/colors.xml create mode 100644 app/sdks/client-android/example/src/main/res/values/strings.xml create mode 100644 app/sdks/client-android/example/src/main/res/values/themes.xml create mode 100644 app/sdks/client-android/gradle.properties create mode 100644 app/sdks/client-android/gradle/wrapper/gradle-wrapper.jar create mode 100644 app/sdks/client-android/gradle/wrapper/gradle-wrapper.properties create mode 100644 app/sdks/client-android/gradlew create mode 100644 app/sdks/client-android/gradlew.bat create mode 100644 app/sdks/client-android/library/.gitignore create mode 100644 app/sdks/client-android/library/build.gradle create mode 100644 app/sdks/client-android/library/example/README.md create mode 100644 app/sdks/client-android/library/src/main/AndroidManifest.xml create mode 100644 app/sdks/client-android/library/src/main/java/io/appwrite/Client.kt create mode 100644 app/sdks/client-android/library/src/main/java/io/appwrite/KeepAliveService.kt create mode 100644 app/sdks/client-android/library/src/main/java/io/appwrite/WebAuthComponent.kt create mode 100644 app/sdks/client-android/library/src/main/java/io/appwrite/exceptions/AppwriteException.kt create mode 100644 app/sdks/client-android/library/src/main/java/io/appwrite/extensions/JsonExtensions.kt create mode 100644 app/sdks/client-android/library/src/main/java/io/appwrite/models/Error.kt create mode 100644 app/sdks/client-android/library/src/main/java/io/appwrite/services/Account.kt create mode 100644 app/sdks/client-android/library/src/main/java/io/appwrite/services/Avatars.kt create mode 100644 app/sdks/client-android/library/src/main/java/io/appwrite/services/BaseService.kt create mode 100644 app/sdks/client-android/library/src/main/java/io/appwrite/services/Database.kt create mode 100644 app/sdks/client-android/library/src/main/java/io/appwrite/services/Functions.kt create mode 100644 app/sdks/client-android/library/src/main/java/io/appwrite/services/Locale.kt create mode 100644 app/sdks/client-android/library/src/main/java/io/appwrite/services/Storage.kt create mode 100644 app/sdks/client-android/library/src/main/java/io/appwrite/services/Teams.kt create mode 100644 app/sdks/client-android/library/src/main/java/io/appwrite/views/CallbackActivity.kt create mode 100644 app/sdks/client-android/scripts/publish-config.gradle create mode 100644 app/sdks/client-android/scripts/publish-module.gradle create mode 100644 app/sdks/client-android/settings.gradle create mode 100644 docs/examples/0.8.x/client-android/examples/account/create-anonymous-session.md create mode 100644 docs/examples/0.8.x/client-android/examples/account/create-j-w-t.md create mode 100644 docs/examples/0.8.x/client-android/examples/account/create-o-auth2session.md create mode 100644 docs/examples/0.8.x/client-android/examples/account/create-recovery.md create mode 100644 docs/examples/0.8.x/client-android/examples/account/create-session.md create mode 100644 docs/examples/0.8.x/client-android/examples/account/create-verification.md create mode 100644 docs/examples/0.8.x/client-android/examples/account/create.md create mode 100644 docs/examples/0.8.x/client-android/examples/account/delete-session.md create mode 100644 docs/examples/0.8.x/client-android/examples/account/delete-sessions.md create mode 100644 docs/examples/0.8.x/client-android/examples/account/delete.md create mode 100644 docs/examples/0.8.x/client-android/examples/account/get-logs.md create mode 100644 docs/examples/0.8.x/client-android/examples/account/get-prefs.md create mode 100644 docs/examples/0.8.x/client-android/examples/account/get-sessions.md create mode 100644 docs/examples/0.8.x/client-android/examples/account/get.md create mode 100644 docs/examples/0.8.x/client-android/examples/account/update-email.md create mode 100644 docs/examples/0.8.x/client-android/examples/account/update-name.md create mode 100644 docs/examples/0.8.x/client-android/examples/account/update-password.md create mode 100644 docs/examples/0.8.x/client-android/examples/account/update-prefs.md create mode 100644 docs/examples/0.8.x/client-android/examples/account/update-recovery.md create mode 100644 docs/examples/0.8.x/client-android/examples/account/update-verification.md create mode 100644 docs/examples/0.8.x/client-android/examples/avatars/get-browser.md create mode 100644 docs/examples/0.8.x/client-android/examples/avatars/get-credit-card.md create mode 100644 docs/examples/0.8.x/client-android/examples/avatars/get-favicon.md create mode 100644 docs/examples/0.8.x/client-android/examples/avatars/get-flag.md create mode 100644 docs/examples/0.8.x/client-android/examples/avatars/get-image.md create mode 100644 docs/examples/0.8.x/client-android/examples/avatars/get-initials.md create mode 100644 docs/examples/0.8.x/client-android/examples/avatars/get-q-r.md create mode 100644 docs/examples/0.8.x/client-android/examples/database/create-document.md create mode 100644 docs/examples/0.8.x/client-android/examples/database/delete-document.md create mode 100644 docs/examples/0.8.x/client-android/examples/database/get-document.md create mode 100644 docs/examples/0.8.x/client-android/examples/database/list-documents.md create mode 100644 docs/examples/0.8.x/client-android/examples/database/update-document.md create mode 100644 docs/examples/0.8.x/client-android/examples/functions/create-execution.md create mode 100644 docs/examples/0.8.x/client-android/examples/functions/get-execution.md create mode 100644 docs/examples/0.8.x/client-android/examples/functions/list-executions.md create mode 100644 docs/examples/0.8.x/client-android/examples/locale/get-continents.md create mode 100644 docs/examples/0.8.x/client-android/examples/locale/get-countries-e-u.md create mode 100644 docs/examples/0.8.x/client-android/examples/locale/get-countries-phones.md create mode 100644 docs/examples/0.8.x/client-android/examples/locale/get-countries.md create mode 100644 docs/examples/0.8.x/client-android/examples/locale/get-currencies.md create mode 100644 docs/examples/0.8.x/client-android/examples/locale/get-languages.md create mode 100644 docs/examples/0.8.x/client-android/examples/locale/get.md create mode 100644 docs/examples/0.8.x/client-android/examples/storage/create-file.md create mode 100644 docs/examples/0.8.x/client-android/examples/storage/delete-file.md create mode 100644 docs/examples/0.8.x/client-android/examples/storage/get-file-download.md create mode 100644 docs/examples/0.8.x/client-android/examples/storage/get-file-preview.md create mode 100644 docs/examples/0.8.x/client-android/examples/storage/get-file-view.md create mode 100644 docs/examples/0.8.x/client-android/examples/storage/get-file.md create mode 100644 docs/examples/0.8.x/client-android/examples/storage/list-files.md create mode 100644 docs/examples/0.8.x/client-android/examples/storage/update-file.md create mode 100644 docs/examples/0.8.x/client-android/examples/teams/create-membership.md create mode 100644 docs/examples/0.8.x/client-android/examples/teams/create.md create mode 100644 docs/examples/0.8.x/client-android/examples/teams/delete-membership.md create mode 100644 docs/examples/0.8.x/client-android/examples/teams/delete.md create mode 100644 docs/examples/0.8.x/client-android/examples/teams/get-memberships.md create mode 100644 docs/examples/0.8.x/client-android/examples/teams/get.md create mode 100644 docs/examples/0.8.x/client-android/examples/teams/list.md create mode 100644 docs/examples/0.8.x/client-android/examples/teams/update-membership-roles.md create mode 100644 docs/examples/0.8.x/client-android/examples/teams/update-membership-status.md create mode 100644 docs/examples/0.8.x/client-android/examples/teams/update.md diff --git a/app/sdks/client-android/.github/workflows/publish.yml b/app/sdks/client-android/.github/workflows/publish.yml new file mode 100644 index 0000000000..b777478bfb --- /dev/null +++ b/app/sdks/client-android/.github/workflows/publish.yml @@ -0,0 +1,53 @@ +name: Publish to Maven Central + +# Run this workflow when a release is created +on: + release: + types: [released] + +jobs: + publish: + name: Release build and publish + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v2 + - name: Set up JDK 1.8 + uses: actions/setup-java@v1 + with: + java-version: 1.8 + # Base64 decodes and pipes the GPG key content into the secret file + - name: Prepare environment + env: + GPG_KEY_CONTENTS: ${{ secrets.GPG_KEY_CONTENTS }} + SIGNING_SECRET_KEY_RING_FILE: ${{ secrets.SIGNING_SECRET_KEY_RING_FILE }} + run: | + git fetch --unshallow + sudo bash -c "echo '$GPG_KEY_CONTENTS' | base64 -d > '$SIGNING_SECRET_KEY_RING_FILE'" + chmod +x ./gradlew + + # Builds the release artifacts of the library + - name: Build Release Artifacts + run: ./gradlew --info library:assembleRelease + + # Generates other artifacts (javadocJar is optional) + - name: Generate Source jar + run: ./gradlew javadocJar + + # Runs upload, and then closes & releases the repository + - name: Publish Release Version to MavenCentral + run: | + if ${{ endswith(github.event.release.tag_name, '-SNAPSHOT') }}; then + echo "Publising Snapshot Version ${{ github.event.release.tag_name}} to Snapshot repository" + ./gradlew publishReleasePublicationToSonatypeRepository + else + echo "Publising Release Version ${{ github.event.release.tag_name}} to Staging repository" + ./gradlew publishReleasePublicationToSonatypeRepository --max-workers 1 closeAndReleaseSonatypeStagingRepository + fi + env: + OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }} + OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }} + SIGNING_KEY_ID: ${{ secrets.SIGNING_KEY_ID }} + SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }} + SIGNING_SECRET_KEY_RING_FILE: ${{ secrets.SIGNING_SECRET_KEY_RING_FILE }} + SONATYPE_STAGING_PROFILE_ID: ${{ secrets.SONATYPE_STAGING_PROFILE_ID }} \ No newline at end of file diff --git a/app/sdks/client-android/.gitignore b/app/sdks/client-android/.gitignore new file mode 100644 index 0000000000..36fb932326 --- /dev/null +++ b/app/sdks/client-android/.gitignore @@ -0,0 +1,12 @@ +*.iml +.gradle +/local.properties +/.idea/* +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +.local.properties +.env +*/build \ No newline at end of file diff --git a/app/sdks/client-android/CHANGELOG.md b/app/sdks/client-android/CHANGELOG.md new file mode 100644 index 0000000000..fa4d35e687 --- /dev/null +++ b/app/sdks/client-android/CHANGELOG.md @@ -0,0 +1 @@ +# Change Log \ No newline at end of file diff --git a/app/sdks/client-android/LICENSE.md b/app/sdks/client-android/LICENSE.md new file mode 100644 index 0000000000..d73a6e9829 --- /dev/null +++ b/app/sdks/client-android/LICENSE.md @@ -0,0 +1,12 @@ +Copyright (c) 2021 Appwrite (https://appwrite.io) and individual contributors. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + + 3. Neither the name Appwrite nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/app/sdks/client-android/README.md b/app/sdks/client-android/README.md new file mode 100644 index 0000000000..87260ade47 --- /dev/null +++ b/app/sdks/client-android/README.md @@ -0,0 +1,157 @@ +# Appwrite Android SDK + +![License](https://img.shields.io/github/license/appwrite/sdk-for-android.svg?style=flat-square) +![Version](https://img.shields.io/badge/api%20version-0.8.0-blue.svg?style=flat-square) +[![Twitter Account](https://img.shields.io/twitter/follow/appwrite_io?color=00acee&label=twitter&style=flat-square)](https://twitter.com/appwrite_io) +[![Discord](https://img.shields.io/discord/564160730845151244?label=discord&style=flat-square)](https://appwrite.io/discord) + +**This SDK is compatible with Appwrite server version 0.8.x. For older versions, please check [previous releases](https://github.com/appwrite/sdk-for-android/releases).** + +Appwrite is an open-source backend as a service server that abstract and simplify complex and repetitive development tasks behind a very simple to use REST API. Appwrite aims to help you develop your apps faster and in a more secure way. Use the Android SDK to integrate your app with the Appwrite server to easily start interacting with all of Appwrite backend APIs and tools. For full API documentation and tutorials go to [https://appwrite.io/docs](https://appwrite.io/docs) + +![Appwrite](https://appwrite.io/images/github.png) + +## Installation + +### Gradle + +Appwrite's Android SDK is hosted on Maven Central. In order to fetch the Appwrite SDK, add this to your root level `build.gradle(.kts)` file: + +```groovy +repositories { + mavenCentral() +} +``` + +If you would like to fetch our SNAPSHOT releases, you need to add the SNAPSHOT maven repository to your `build.gradle(.kts)`: + +```groovy +repositories { + maven { + url "https://s01.oss.sonatype.org/content/repositories/snapshots/" + } +} +``` + +Next, add the dependency to your project's `build.gradle(.kts)` file: + +```groovy +implementation("io.appwrite:sdk-for-android:0.0.0-SNAPSHOT") +``` + +### Maven +Add this to your project's `pom.xml` file: + +```xml + + + io.appwrite + sdk-for-android + 0.0.0-SNAPSHOT + + +``` + + +## Getting Started + +### Add your Android Platform +To initialize your SDK and start interacting with Appwrite services, you need to add a new Android platform to your project. To add a new platform, go to your Appwrite console, select your project (create one if you haven't already), and click the 'Add Platform' button on the project Dashboard. + +From the options, choose to add a new **Android** platform and add your app credentials. + +Add your app name and package name. Your package name is generally the applicationId in your app-level `build.gradle` file. By registering a new platform, you are allowing your app to communicate with the Appwrite API. + +### Registering additional activities +In order to capture the Appwrite OAuth callback url, the following activity needs to be added to your [AndroidManifest.xml](https://github.com/appwrite/playground-for-android/blob/master/app/src/main/AndroidManifest.xml). Be sure to replace the **[PROJECT_ID]** string with your actual Appwrite project ID. You can find your Appwrite project ID in your project settings screen in the console. + +```xml + + + + + + + + + + + + +``` + +### Init your SDK + +

    Initialize your SDK with your Appwrite server API endpoint and project ID, which can be found in your project settings page. + +```kotlin +import io.appwrite.Client +import io.appwrite.services.Account + +val client = Client(context) + .setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint + .setProject("5df5acd0d48c2") // Your project ID + .setSelfSigned(true) // Remove in production +``` + +Before starting to send any API calls to your new Appwrite instance, make sure your Android emulators has network access to the Appwrite server hostname or IP address. + +When trying to connect to Appwrite from an emulator or a mobile device, localhost is the hostname of the device or emulator and not your local Appwrite instance. You should replace localhost with your private IP. You can also use a service like [ngrok](https://ngrok.com/) to proxy the Appwrite API. + +### Make Your First Request + +

    Once your SDK object is set, access any of the Appwrite services and choose any request to send. Full documentation for any service method you would like to use can be found in your SDK documentation or in the [API References](https://appwrite.io/docs) section. + +```kotlin +// Register User +val account = Account(client) +val response = account.create( + "email@example.com", + "password" +) +``` + +### Full Example + +```kotlin +import io.appwrite.Client +import io.appwrite.services.Account + +val client = Client(context) + .setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint + .setProject("5df5acd0d48c2") // Your project ID + .setSelfSigned(true) // Remove in production + +val account = Account(client) +val response = account.create( + "email@example.com", + "password" +) +``` + +### Error Handling +The Appwrite Android SDK raises an `AppwriteException` object with `message`, `code` and `response` properties. You can handle any errors by catching `AppwriteException` and present the `message` to the user or handle it yourself based on the provided error information. Below is an example. + +```kotlin +try { + var response = account.create("email@example.com", "password") + Log.d("Appwrite response", response.body?.string()) +} catch(e : AppwriteException) { + Log.e("AppwriteException",e.message.toString()) +} +``` + +### Learn more +You can use following resources to learn more and get help +- 🚀 [Getting Started Tutorial](https://appwrite.io/docs/getting-started-for-android) +- 📜 [Appwrite Docs](https://appwrite.io/docs) +- 💬 [Discord Community](https://appwrite.io/discord) +- 🚂 [Appwrite Android Playground](https://github.com/appwrite/playground-for-android) + +## Contribution + +This library is auto-generated by Appwrite custom [SDK Generator](https://github.com/appwrite/sdk-generator). To learn more about how you can help us improve this SDK, please check the [contribution guide](https://github.com/appwrite/sdk-generator/blob/master/CONTRIBUTING.md) before sending a pull-request. + +## License + +Please see the [BSD-3-Clause license](https://raw.githubusercontent.com/appwrite/appwrite/master/LICENSE) file for more information. \ No newline at end of file diff --git a/app/sdks/client-android/build.gradle b/app/sdks/client-android/build.gradle new file mode 100644 index 0000000000..6503509470 --- /dev/null +++ b/app/sdks/client-android/build.gradle @@ -0,0 +1,36 @@ +apply plugin: 'io.github.gradle-nexus.publish-plugin' + +// Top-level build file where you can add configuration options common to all sub-projects/modules. +buildscript { + ext.kotlin_version = "1.4.31" + version '0.0.0-SNAPSHOT' + repositories { + maven { url "https://plugins.gradle.org/m2/" } + google() + mavenCentral() + } + dependencies { + classpath "com.android.tools.build:gradle:4.2.0" + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath 'io.github.gradle-nexus:publish-plugin:1.1.0' + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + maven { url "https://jitpack.io" } + google() + mavenCentral() + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} + + +apply from: "${rootDir}/scripts/publish-config.gradle" + diff --git a/app/sdks/client-android/docs/examples/account/create-anonymous-session.md b/app/sdks/client-android/docs/examples/account/create-anonymous-session.md new file mode 100644 index 0000000000..9aa6d9002a --- /dev/null +++ b/app/sdks/client-android/docs/examples/account/create-anonymous-session.md @@ -0,0 +1,10 @@ +import io.appwrite.Client +import io.appwrite.services.Account + +val client = Client(context) + .setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint + .setProject("5df5acd0d48c2") // Your project ID + +val accountService = Account(client) +val response = accountService.createAnonymousSession() +val json = response.body?.string() \ No newline at end of file diff --git a/app/sdks/client-android/docs/examples/account/create-j-w-t.md b/app/sdks/client-android/docs/examples/account/create-j-w-t.md new file mode 100644 index 0000000000..50965da19a --- /dev/null +++ b/app/sdks/client-android/docs/examples/account/create-j-w-t.md @@ -0,0 +1,10 @@ +import io.appwrite.Client +import io.appwrite.services.Account + +val client = Client(context) + .setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint + .setProject("5df5acd0d48c2") // Your project ID + +val accountService = Account(client) +val response = accountService.createJWT() +val json = response.body?.string() \ No newline at end of file diff --git a/app/sdks/client-android/docs/examples/account/create-o-auth2session.md b/app/sdks/client-android/docs/examples/account/create-o-auth2session.md new file mode 100644 index 0000000000..8eaa4aff76 --- /dev/null +++ b/app/sdks/client-android/docs/examples/account/create-o-auth2session.md @@ -0,0 +1,10 @@ +import io.appwrite.Client +import io.appwrite.services.Account + +val client = Client(context) + .setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint + .setProject("5df5acd0d48c2") // Your project ID + +val accountService = Account(client) +val response = accountService.createOAuth2Session("amazon") +val json = response.body?.string() \ No newline at end of file diff --git a/app/sdks/client-android/docs/examples/account/create-recovery.md b/app/sdks/client-android/docs/examples/account/create-recovery.md new file mode 100644 index 0000000000..c43a0e3f74 --- /dev/null +++ b/app/sdks/client-android/docs/examples/account/create-recovery.md @@ -0,0 +1,10 @@ +import io.appwrite.Client +import io.appwrite.services.Account + +val client = Client(context) + .setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint + .setProject("5df5acd0d48c2") // Your project ID + +val accountService = Account(client) +val response = accountService.createRecovery("email@example.com", "https://example.com") +val json = response.body?.string() \ No newline at end of file diff --git a/app/sdks/client-android/docs/examples/account/create-session.md b/app/sdks/client-android/docs/examples/account/create-session.md new file mode 100644 index 0000000000..9940d99e41 --- /dev/null +++ b/app/sdks/client-android/docs/examples/account/create-session.md @@ -0,0 +1,10 @@ +import io.appwrite.Client +import io.appwrite.services.Account + +val client = Client(context) + .setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint + .setProject("5df5acd0d48c2") // Your project ID + +val accountService = Account(client) +val response = accountService.createSession("email@example.com", "password") +val json = response.body?.string() \ No newline at end of file diff --git a/app/sdks/client-android/docs/examples/account/create-verification.md b/app/sdks/client-android/docs/examples/account/create-verification.md new file mode 100644 index 0000000000..cf568233ea --- /dev/null +++ b/app/sdks/client-android/docs/examples/account/create-verification.md @@ -0,0 +1,10 @@ +import io.appwrite.Client +import io.appwrite.services.Account + +val client = Client(context) + .setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint + .setProject("5df5acd0d48c2") // Your project ID + +val accountService = Account(client) +val response = accountService.createVerification("https://example.com") +val json = response.body?.string() \ No newline at end of file diff --git a/app/sdks/client-android/docs/examples/account/create.md b/app/sdks/client-android/docs/examples/account/create.md new file mode 100644 index 0000000000..0036d538b2 --- /dev/null +++ b/app/sdks/client-android/docs/examples/account/create.md @@ -0,0 +1,10 @@ +import io.appwrite.Client +import io.appwrite.services.Account + +val client = Client(context) + .setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint + .setProject("5df5acd0d48c2") // Your project ID + +val accountService = Account(client) +val response = accountService.create("email@example.com", "password") +val json = response.body?.string() \ No newline at end of file diff --git a/app/sdks/client-android/docs/examples/account/delete-session.md b/app/sdks/client-android/docs/examples/account/delete-session.md new file mode 100644 index 0000000000..5bb6aec0ff --- /dev/null +++ b/app/sdks/client-android/docs/examples/account/delete-session.md @@ -0,0 +1,10 @@ +import io.appwrite.Client +import io.appwrite.services.Account + +val client = Client(context) + .setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint + .setProject("5df5acd0d48c2") // Your project ID + +val accountService = Account(client) +val response = accountService.deleteSession("[SESSION_ID]") +val json = response.body?.string() \ No newline at end of file diff --git a/app/sdks/client-android/docs/examples/account/delete-sessions.md b/app/sdks/client-android/docs/examples/account/delete-sessions.md new file mode 100644 index 0000000000..4f700bd921 --- /dev/null +++ b/app/sdks/client-android/docs/examples/account/delete-sessions.md @@ -0,0 +1,10 @@ +import io.appwrite.Client +import io.appwrite.services.Account + +val client = Client(context) + .setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint + .setProject("5df5acd0d48c2") // Your project ID + +val accountService = Account(client) +val response = accountService.deleteSessions() +val json = response.body?.string() \ No newline at end of file diff --git a/app/sdks/client-android/docs/examples/account/delete.md b/app/sdks/client-android/docs/examples/account/delete.md new file mode 100644 index 0000000000..c8e7790112 --- /dev/null +++ b/app/sdks/client-android/docs/examples/account/delete.md @@ -0,0 +1,10 @@ +import io.appwrite.Client +import io.appwrite.services.Account + +val client = Client(context) + .setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint + .setProject("5df5acd0d48c2") // Your project ID + +val accountService = Account(client) +val response = accountService.delete() +val json = response.body?.string() \ No newline at end of file diff --git a/app/sdks/client-android/docs/examples/account/get-logs.md b/app/sdks/client-android/docs/examples/account/get-logs.md new file mode 100644 index 0000000000..65a7fdf44b --- /dev/null +++ b/app/sdks/client-android/docs/examples/account/get-logs.md @@ -0,0 +1,10 @@ +import io.appwrite.Client +import io.appwrite.services.Account + +val client = Client(context) + .setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint + .setProject("5df5acd0d48c2") // Your project ID + +val accountService = Account(client) +val response = accountService.getLogs() +val json = response.body?.string() \ No newline at end of file diff --git a/app/sdks/client-android/docs/examples/account/get-prefs.md b/app/sdks/client-android/docs/examples/account/get-prefs.md new file mode 100644 index 0000000000..355f89812c --- /dev/null +++ b/app/sdks/client-android/docs/examples/account/get-prefs.md @@ -0,0 +1,10 @@ +import io.appwrite.Client +import io.appwrite.services.Account + +val client = Client(context) + .setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint + .setProject("5df5acd0d48c2") // Your project ID + +val accountService = Account(client) +val response = accountService.getPrefs() +val json = response.body?.string() \ No newline at end of file diff --git a/app/sdks/client-android/docs/examples/account/get-sessions.md b/app/sdks/client-android/docs/examples/account/get-sessions.md new file mode 100644 index 0000000000..4da469aff2 --- /dev/null +++ b/app/sdks/client-android/docs/examples/account/get-sessions.md @@ -0,0 +1,10 @@ +import io.appwrite.Client +import io.appwrite.services.Account + +val client = Client(context) + .setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint + .setProject("5df5acd0d48c2") // Your project ID + +val accountService = Account(client) +val response = accountService.getSessions() +val json = response.body?.string() \ No newline at end of file diff --git a/app/sdks/client-android/docs/examples/account/get.md b/app/sdks/client-android/docs/examples/account/get.md new file mode 100644 index 0000000000..f5533f5ae8 --- /dev/null +++ b/app/sdks/client-android/docs/examples/account/get.md @@ -0,0 +1,10 @@ +import io.appwrite.Client +import io.appwrite.services.Account + +val client = Client(context) + .setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint + .setProject("5df5acd0d48c2") // Your project ID + +val accountService = Account(client) +val response = accountService.get() +val json = response.body?.string() \ No newline at end of file diff --git a/app/sdks/client-android/docs/examples/account/update-email.md b/app/sdks/client-android/docs/examples/account/update-email.md new file mode 100644 index 0000000000..d9d10afc29 --- /dev/null +++ b/app/sdks/client-android/docs/examples/account/update-email.md @@ -0,0 +1,10 @@ +import io.appwrite.Client +import io.appwrite.services.Account + +val client = Client(context) + .setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint + .setProject("5df5acd0d48c2") // Your project ID + +val accountService = Account(client) +val response = accountService.updateEmail("email@example.com", "password") +val json = response.body?.string() \ No newline at end of file diff --git a/app/sdks/client-android/docs/examples/account/update-name.md b/app/sdks/client-android/docs/examples/account/update-name.md new file mode 100644 index 0000000000..5854161254 --- /dev/null +++ b/app/sdks/client-android/docs/examples/account/update-name.md @@ -0,0 +1,10 @@ +import io.appwrite.Client +import io.appwrite.services.Account + +val client = Client(context) + .setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint + .setProject("5df5acd0d48c2") // Your project ID + +val accountService = Account(client) +val response = accountService.updateName("[NAME]") +val json = response.body?.string() \ No newline at end of file diff --git a/app/sdks/client-android/docs/examples/account/update-password.md b/app/sdks/client-android/docs/examples/account/update-password.md new file mode 100644 index 0000000000..4f4fe7bd14 --- /dev/null +++ b/app/sdks/client-android/docs/examples/account/update-password.md @@ -0,0 +1,10 @@ +import io.appwrite.Client +import io.appwrite.services.Account + +val client = Client(context) + .setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint + .setProject("5df5acd0d48c2") // Your project ID + +val accountService = Account(client) +val response = accountService.updatePassword("password") +val json = response.body?.string() \ No newline at end of file diff --git a/app/sdks/client-android/docs/examples/account/update-prefs.md b/app/sdks/client-android/docs/examples/account/update-prefs.md new file mode 100644 index 0000000000..dcbf4b6142 --- /dev/null +++ b/app/sdks/client-android/docs/examples/account/update-prefs.md @@ -0,0 +1,10 @@ +import io.appwrite.Client +import io.appwrite.services.Account + +val client = Client(context) + .setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint + .setProject("5df5acd0d48c2") // Your project ID + +val accountService = Account(client) +val response = accountService.updatePrefs({}) +val json = response.body?.string() \ No newline at end of file diff --git a/app/sdks/client-android/docs/examples/account/update-recovery.md b/app/sdks/client-android/docs/examples/account/update-recovery.md new file mode 100644 index 0000000000..05d04f5e8f --- /dev/null +++ b/app/sdks/client-android/docs/examples/account/update-recovery.md @@ -0,0 +1,10 @@ +import io.appwrite.Client +import io.appwrite.services.Account + +val client = Client(context) + .setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint + .setProject("5df5acd0d48c2") // Your project ID + +val accountService = Account(client) +val response = accountService.updateRecovery("[USER_ID]", "[SECRET]", "password", "password") +val json = response.body?.string() \ No newline at end of file diff --git a/app/sdks/client-android/docs/examples/account/update-verification.md b/app/sdks/client-android/docs/examples/account/update-verification.md new file mode 100644 index 0000000000..f924c3d7a5 --- /dev/null +++ b/app/sdks/client-android/docs/examples/account/update-verification.md @@ -0,0 +1,10 @@ +import io.appwrite.Client +import io.appwrite.services.Account + +val client = Client(context) + .setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint + .setProject("5df5acd0d48c2") // Your project ID + +val accountService = Account(client) +val response = accountService.updateVerification("[USER_ID]", "[SECRET]") +val json = response.body?.string() \ No newline at end of file diff --git a/app/sdks/client-android/docs/examples/avatars/get-browser.md b/app/sdks/client-android/docs/examples/avatars/get-browser.md new file mode 100644 index 0000000000..642ae8c164 --- /dev/null +++ b/app/sdks/client-android/docs/examples/avatars/get-browser.md @@ -0,0 +1,10 @@ +import io.appwrite.Client +import io.appwrite.services.Avatars + +val client = Client(context) + .setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint + .setProject("5df5acd0d48c2") // Your project ID + +val avatarsService = Avatars(client) +val response = avatarsService.getBrowser("aa") +val json = response.body?.string() \ No newline at end of file diff --git a/app/sdks/client-android/docs/examples/avatars/get-credit-card.md b/app/sdks/client-android/docs/examples/avatars/get-credit-card.md new file mode 100644 index 0000000000..4fba61f1f2 --- /dev/null +++ b/app/sdks/client-android/docs/examples/avatars/get-credit-card.md @@ -0,0 +1,10 @@ +import io.appwrite.Client +import io.appwrite.services.Avatars + +val client = Client(context) + .setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint + .setProject("5df5acd0d48c2") // Your project ID + +val avatarsService = Avatars(client) +val response = avatarsService.getCreditCard("amex") +val json = response.body?.string() \ No newline at end of file diff --git a/app/sdks/client-android/docs/examples/avatars/get-favicon.md b/app/sdks/client-android/docs/examples/avatars/get-favicon.md new file mode 100644 index 0000000000..bef7fd481f --- /dev/null +++ b/app/sdks/client-android/docs/examples/avatars/get-favicon.md @@ -0,0 +1,10 @@ +import io.appwrite.Client +import io.appwrite.services.Avatars + +val client = Client(context) + .setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint + .setProject("5df5acd0d48c2") // Your project ID + +val avatarsService = Avatars(client) +val response = avatarsService.getFavicon("https://example.com") +val json = response.body?.string() \ No newline at end of file diff --git a/app/sdks/client-android/docs/examples/avatars/get-flag.md b/app/sdks/client-android/docs/examples/avatars/get-flag.md new file mode 100644 index 0000000000..0c4ec2ddda --- /dev/null +++ b/app/sdks/client-android/docs/examples/avatars/get-flag.md @@ -0,0 +1,10 @@ +import io.appwrite.Client +import io.appwrite.services.Avatars + +val client = Client(context) + .setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint + .setProject("5df5acd0d48c2") // Your project ID + +val avatarsService = Avatars(client) +val response = avatarsService.getFlag("af") +val json = response.body?.string() \ No newline at end of file diff --git a/app/sdks/client-android/docs/examples/avatars/get-image.md b/app/sdks/client-android/docs/examples/avatars/get-image.md new file mode 100644 index 0000000000..8cec3e79af --- /dev/null +++ b/app/sdks/client-android/docs/examples/avatars/get-image.md @@ -0,0 +1,10 @@ +import io.appwrite.Client +import io.appwrite.services.Avatars + +val client = Client(context) + .setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint + .setProject("5df5acd0d48c2") // Your project ID + +val avatarsService = Avatars(client) +val response = avatarsService.getImage("https://example.com") +val json = response.body?.string() \ No newline at end of file diff --git a/app/sdks/client-android/docs/examples/avatars/get-initials.md b/app/sdks/client-android/docs/examples/avatars/get-initials.md new file mode 100644 index 0000000000..4ddc263327 --- /dev/null +++ b/app/sdks/client-android/docs/examples/avatars/get-initials.md @@ -0,0 +1,10 @@ +import io.appwrite.Client +import io.appwrite.services.Avatars + +val client = Client(context) + .setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint + .setProject("5df5acd0d48c2") // Your project ID + +val avatarsService = Avatars(client) +val response = avatarsService.getInitials() +val json = response.body?.string() \ No newline at end of file diff --git a/app/sdks/client-android/docs/examples/avatars/get-q-r.md b/app/sdks/client-android/docs/examples/avatars/get-q-r.md new file mode 100644 index 0000000000..57eb765733 --- /dev/null +++ b/app/sdks/client-android/docs/examples/avatars/get-q-r.md @@ -0,0 +1,10 @@ +import io.appwrite.Client +import io.appwrite.services.Avatars + +val client = Client(context) + .setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint + .setProject("5df5acd0d48c2") // Your project ID + +val avatarsService = Avatars(client) +val response = avatarsService.getQR("[TEXT]") +val json = response.body?.string() \ No newline at end of file diff --git a/app/sdks/client-android/docs/examples/database/create-document.md b/app/sdks/client-android/docs/examples/database/create-document.md new file mode 100644 index 0000000000..95959f5676 --- /dev/null +++ b/app/sdks/client-android/docs/examples/database/create-document.md @@ -0,0 +1,10 @@ +import io.appwrite.Client +import io.appwrite.services.Database + +val client = Client(context) + .setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint + .setProject("5df5acd0d48c2") // Your project ID + +val databaseService = Database(client) +val response = databaseService.createDocument("[COLLECTION_ID]", {}) +val json = response.body?.string() \ No newline at end of file diff --git a/app/sdks/client-android/docs/examples/database/delete-document.md b/app/sdks/client-android/docs/examples/database/delete-document.md new file mode 100644 index 0000000000..6bcaab03d9 --- /dev/null +++ b/app/sdks/client-android/docs/examples/database/delete-document.md @@ -0,0 +1,10 @@ +import io.appwrite.Client +import io.appwrite.services.Database + +val client = Client(context) + .setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint + .setProject("5df5acd0d48c2") // Your project ID + +val databaseService = Database(client) +val response = databaseService.deleteDocument("[COLLECTION_ID]", "[DOCUMENT_ID]") +val json = response.body?.string() \ No newline at end of file diff --git a/app/sdks/client-android/docs/examples/database/get-document.md b/app/sdks/client-android/docs/examples/database/get-document.md new file mode 100644 index 0000000000..a71e71c57f --- /dev/null +++ b/app/sdks/client-android/docs/examples/database/get-document.md @@ -0,0 +1,10 @@ +import io.appwrite.Client +import io.appwrite.services.Database + +val client = Client(context) + .setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint + .setProject("5df5acd0d48c2") // Your project ID + +val databaseService = Database(client) +val response = databaseService.getDocument("[COLLECTION_ID]", "[DOCUMENT_ID]") +val json = response.body?.string() \ No newline at end of file diff --git a/app/sdks/client-android/docs/examples/database/list-documents.md b/app/sdks/client-android/docs/examples/database/list-documents.md new file mode 100644 index 0000000000..0c720e2a5a --- /dev/null +++ b/app/sdks/client-android/docs/examples/database/list-documents.md @@ -0,0 +1,10 @@ +import io.appwrite.Client +import io.appwrite.services.Database + +val client = Client(context) + .setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint + .setProject("5df5acd0d48c2") // Your project ID + +val databaseService = Database(client) +val response = databaseService.listDocuments("[COLLECTION_ID]") +val json = response.body?.string() \ No newline at end of file diff --git a/app/sdks/client-android/docs/examples/database/update-document.md b/app/sdks/client-android/docs/examples/database/update-document.md new file mode 100644 index 0000000000..cb0f8c9f68 --- /dev/null +++ b/app/sdks/client-android/docs/examples/database/update-document.md @@ -0,0 +1,10 @@ +import io.appwrite.Client +import io.appwrite.services.Database + +val client = Client(context) + .setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint + .setProject("5df5acd0d48c2") // Your project ID + +val databaseService = Database(client) +val response = databaseService.updateDocument("[COLLECTION_ID]", "[DOCUMENT_ID]", {}) +val json = response.body?.string() \ No newline at end of file diff --git a/app/sdks/client-android/docs/examples/functions/create-execution.md b/app/sdks/client-android/docs/examples/functions/create-execution.md new file mode 100644 index 0000000000..665c3ceb84 --- /dev/null +++ b/app/sdks/client-android/docs/examples/functions/create-execution.md @@ -0,0 +1,10 @@ +import io.appwrite.Client +import io.appwrite.services.Functions + +val client = Client(context) + .setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint + .setProject("5df5acd0d48c2") // Your project ID + +val functionsService = Functions(client) +val response = functionsService.createExecution("[FUNCTION_ID]") +val json = response.body?.string() \ No newline at end of file diff --git a/app/sdks/client-android/docs/examples/functions/get-execution.md b/app/sdks/client-android/docs/examples/functions/get-execution.md new file mode 100644 index 0000000000..79c365112b --- /dev/null +++ b/app/sdks/client-android/docs/examples/functions/get-execution.md @@ -0,0 +1,10 @@ +import io.appwrite.Client +import io.appwrite.services.Functions + +val client = Client(context) + .setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint + .setProject("5df5acd0d48c2") // Your project ID + +val functionsService = Functions(client) +val response = functionsService.getExecution("[FUNCTION_ID]", "[EXECUTION_ID]") +val json = response.body?.string() \ No newline at end of file diff --git a/app/sdks/client-android/docs/examples/functions/list-executions.md b/app/sdks/client-android/docs/examples/functions/list-executions.md new file mode 100644 index 0000000000..cfb4beddd9 --- /dev/null +++ b/app/sdks/client-android/docs/examples/functions/list-executions.md @@ -0,0 +1,10 @@ +import io.appwrite.Client +import io.appwrite.services.Functions + +val client = Client(context) + .setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint + .setProject("5df5acd0d48c2") // Your project ID + +val functionsService = Functions(client) +val response = functionsService.listExecutions("[FUNCTION_ID]") +val json = response.body?.string() \ No newline at end of file diff --git a/app/sdks/client-android/docs/examples/locale/get-continents.md b/app/sdks/client-android/docs/examples/locale/get-continents.md new file mode 100644 index 0000000000..39db743450 --- /dev/null +++ b/app/sdks/client-android/docs/examples/locale/get-continents.md @@ -0,0 +1,10 @@ +import io.appwrite.Client +import io.appwrite.services.Locale + +val client = Client(context) + .setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint + .setProject("5df5acd0d48c2") // Your project ID + +val localeService = Locale(client) +val response = localeService.getContinents() +val json = response.body?.string() \ No newline at end of file diff --git a/app/sdks/client-android/docs/examples/locale/get-countries-e-u.md b/app/sdks/client-android/docs/examples/locale/get-countries-e-u.md new file mode 100644 index 0000000000..32222b45ca --- /dev/null +++ b/app/sdks/client-android/docs/examples/locale/get-countries-e-u.md @@ -0,0 +1,10 @@ +import io.appwrite.Client +import io.appwrite.services.Locale + +val client = Client(context) + .setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint + .setProject("5df5acd0d48c2") // Your project ID + +val localeService = Locale(client) +val response = localeService.getCountriesEU() +val json = response.body?.string() \ No newline at end of file diff --git a/app/sdks/client-android/docs/examples/locale/get-countries-phones.md b/app/sdks/client-android/docs/examples/locale/get-countries-phones.md new file mode 100644 index 0000000000..3dcdac19e9 --- /dev/null +++ b/app/sdks/client-android/docs/examples/locale/get-countries-phones.md @@ -0,0 +1,10 @@ +import io.appwrite.Client +import io.appwrite.services.Locale + +val client = Client(context) + .setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint + .setProject("5df5acd0d48c2") // Your project ID + +val localeService = Locale(client) +val response = localeService.getCountriesPhones() +val json = response.body?.string() \ No newline at end of file diff --git a/app/sdks/client-android/docs/examples/locale/get-countries.md b/app/sdks/client-android/docs/examples/locale/get-countries.md new file mode 100644 index 0000000000..437afe1bb1 --- /dev/null +++ b/app/sdks/client-android/docs/examples/locale/get-countries.md @@ -0,0 +1,10 @@ +import io.appwrite.Client +import io.appwrite.services.Locale + +val client = Client(context) + .setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint + .setProject("5df5acd0d48c2") // Your project ID + +val localeService = Locale(client) +val response = localeService.getCountries() +val json = response.body?.string() \ No newline at end of file diff --git a/app/sdks/client-android/docs/examples/locale/get-currencies.md b/app/sdks/client-android/docs/examples/locale/get-currencies.md new file mode 100644 index 0000000000..38879dab4b --- /dev/null +++ b/app/sdks/client-android/docs/examples/locale/get-currencies.md @@ -0,0 +1,10 @@ +import io.appwrite.Client +import io.appwrite.services.Locale + +val client = Client(context) + .setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint + .setProject("5df5acd0d48c2") // Your project ID + +val localeService = Locale(client) +val response = localeService.getCurrencies() +val json = response.body?.string() \ No newline at end of file diff --git a/app/sdks/client-android/docs/examples/locale/get-languages.md b/app/sdks/client-android/docs/examples/locale/get-languages.md new file mode 100644 index 0000000000..78c3bcaef6 --- /dev/null +++ b/app/sdks/client-android/docs/examples/locale/get-languages.md @@ -0,0 +1,10 @@ +import io.appwrite.Client +import io.appwrite.services.Locale + +val client = Client(context) + .setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint + .setProject("5df5acd0d48c2") // Your project ID + +val localeService = Locale(client) +val response = localeService.getLanguages() +val json = response.body?.string() \ No newline at end of file diff --git a/app/sdks/client-android/docs/examples/locale/get.md b/app/sdks/client-android/docs/examples/locale/get.md new file mode 100644 index 0000000000..6552b21de5 --- /dev/null +++ b/app/sdks/client-android/docs/examples/locale/get.md @@ -0,0 +1,10 @@ +import io.appwrite.Client +import io.appwrite.services.Locale + +val client = Client(context) + .setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint + .setProject("5df5acd0d48c2") // Your project ID + +val localeService = Locale(client) +val response = localeService.get() +val json = response.body?.string() \ No newline at end of file diff --git a/app/sdks/client-android/docs/examples/storage/create-file.md b/app/sdks/client-android/docs/examples/storage/create-file.md new file mode 100644 index 0000000000..9e2d8c53b3 --- /dev/null +++ b/app/sdks/client-android/docs/examples/storage/create-file.md @@ -0,0 +1,10 @@ +import io.appwrite.Client +import io.appwrite.services.Storage + +val client = Client(context) + .setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint + .setProject("5df5acd0d48c2") // Your project ID + +val storageService = Storage(client) +val response = storageService.createFile(new File("./path-to-files/image.jpg")) +val json = response.body?.string() \ No newline at end of file diff --git a/app/sdks/client-android/docs/examples/storage/delete-file.md b/app/sdks/client-android/docs/examples/storage/delete-file.md new file mode 100644 index 0000000000..87479bd086 --- /dev/null +++ b/app/sdks/client-android/docs/examples/storage/delete-file.md @@ -0,0 +1,10 @@ +import io.appwrite.Client +import io.appwrite.services.Storage + +val client = Client(context) + .setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint + .setProject("5df5acd0d48c2") // Your project ID + +val storageService = Storage(client) +val response = storageService.deleteFile("[FILE_ID]") +val json = response.body?.string() \ No newline at end of file diff --git a/app/sdks/client-android/docs/examples/storage/get-file-download.md b/app/sdks/client-android/docs/examples/storage/get-file-download.md new file mode 100644 index 0000000000..25681e1b0f --- /dev/null +++ b/app/sdks/client-android/docs/examples/storage/get-file-download.md @@ -0,0 +1,10 @@ +import io.appwrite.Client +import io.appwrite.services.Storage + +val client = Client(context) + .setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint + .setProject("5df5acd0d48c2") // Your project ID + +val storageService = Storage(client) +val response = storageService.getFileDownload("[FILE_ID]") +val json = response.body?.string() \ No newline at end of file diff --git a/app/sdks/client-android/docs/examples/storage/get-file-preview.md b/app/sdks/client-android/docs/examples/storage/get-file-preview.md new file mode 100644 index 0000000000..a3c317c316 --- /dev/null +++ b/app/sdks/client-android/docs/examples/storage/get-file-preview.md @@ -0,0 +1,10 @@ +import io.appwrite.Client +import io.appwrite.services.Storage + +val client = Client(context) + .setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint + .setProject("5df5acd0d48c2") // Your project ID + +val storageService = Storage(client) +val response = storageService.getFilePreview("[FILE_ID]") +val json = response.body?.string() \ No newline at end of file diff --git a/app/sdks/client-android/docs/examples/storage/get-file-view.md b/app/sdks/client-android/docs/examples/storage/get-file-view.md new file mode 100644 index 0000000000..d2d0f45348 --- /dev/null +++ b/app/sdks/client-android/docs/examples/storage/get-file-view.md @@ -0,0 +1,10 @@ +import io.appwrite.Client +import io.appwrite.services.Storage + +val client = Client(context) + .setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint + .setProject("5df5acd0d48c2") // Your project ID + +val storageService = Storage(client) +val response = storageService.getFileView("[FILE_ID]") +val json = response.body?.string() \ No newline at end of file diff --git a/app/sdks/client-android/docs/examples/storage/get-file.md b/app/sdks/client-android/docs/examples/storage/get-file.md new file mode 100644 index 0000000000..2d0f0d1394 --- /dev/null +++ b/app/sdks/client-android/docs/examples/storage/get-file.md @@ -0,0 +1,10 @@ +import io.appwrite.Client +import io.appwrite.services.Storage + +val client = Client(context) + .setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint + .setProject("5df5acd0d48c2") // Your project ID + +val storageService = Storage(client) +val response = storageService.getFile("[FILE_ID]") +val json = response.body?.string() \ No newline at end of file diff --git a/app/sdks/client-android/docs/examples/storage/list-files.md b/app/sdks/client-android/docs/examples/storage/list-files.md new file mode 100644 index 0000000000..09327879ff --- /dev/null +++ b/app/sdks/client-android/docs/examples/storage/list-files.md @@ -0,0 +1,10 @@ +import io.appwrite.Client +import io.appwrite.services.Storage + +val client = Client(context) + .setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint + .setProject("5df5acd0d48c2") // Your project ID + +val storageService = Storage(client) +val response = storageService.listFiles() +val json = response.body?.string() \ No newline at end of file diff --git a/app/sdks/client-android/docs/examples/storage/update-file.md b/app/sdks/client-android/docs/examples/storage/update-file.md new file mode 100644 index 0000000000..a9d61aabc3 --- /dev/null +++ b/app/sdks/client-android/docs/examples/storage/update-file.md @@ -0,0 +1,10 @@ +import io.appwrite.Client +import io.appwrite.services.Storage + +val client = Client(context) + .setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint + .setProject("5df5acd0d48c2") // Your project ID + +val storageService = Storage(client) +val response = storageService.updateFile("[FILE_ID]", List(), List()) +val json = response.body?.string() \ No newline at end of file diff --git a/app/sdks/client-android/docs/examples/teams/create-membership.md b/app/sdks/client-android/docs/examples/teams/create-membership.md new file mode 100644 index 0000000000..c1325b85b9 --- /dev/null +++ b/app/sdks/client-android/docs/examples/teams/create-membership.md @@ -0,0 +1,10 @@ +import io.appwrite.Client +import io.appwrite.services.Teams + +val client = Client(context) + .setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint + .setProject("5df5acd0d48c2") // Your project ID + +val teamsService = Teams(client) +val response = teamsService.createMembership("[TEAM_ID]", "email@example.com", List(), "https://example.com") +val json = response.body?.string() \ No newline at end of file diff --git a/app/sdks/client-android/docs/examples/teams/create.md b/app/sdks/client-android/docs/examples/teams/create.md new file mode 100644 index 0000000000..bd72a0584a --- /dev/null +++ b/app/sdks/client-android/docs/examples/teams/create.md @@ -0,0 +1,10 @@ +import io.appwrite.Client +import io.appwrite.services.Teams + +val client = Client(context) + .setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint + .setProject("5df5acd0d48c2") // Your project ID + +val teamsService = Teams(client) +val response = teamsService.create("[NAME]") +val json = response.body?.string() \ No newline at end of file diff --git a/app/sdks/client-android/docs/examples/teams/delete-membership.md b/app/sdks/client-android/docs/examples/teams/delete-membership.md new file mode 100644 index 0000000000..58e29e5986 --- /dev/null +++ b/app/sdks/client-android/docs/examples/teams/delete-membership.md @@ -0,0 +1,10 @@ +import io.appwrite.Client +import io.appwrite.services.Teams + +val client = Client(context) + .setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint + .setProject("5df5acd0d48c2") // Your project ID + +val teamsService = Teams(client) +val response = teamsService.deleteMembership("[TEAM_ID]", "[MEMBERSHIP_ID]") +val json = response.body?.string() \ No newline at end of file diff --git a/app/sdks/client-android/docs/examples/teams/delete.md b/app/sdks/client-android/docs/examples/teams/delete.md new file mode 100644 index 0000000000..11db6a81b1 --- /dev/null +++ b/app/sdks/client-android/docs/examples/teams/delete.md @@ -0,0 +1,10 @@ +import io.appwrite.Client +import io.appwrite.services.Teams + +val client = Client(context) + .setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint + .setProject("5df5acd0d48c2") // Your project ID + +val teamsService = Teams(client) +val response = teamsService.delete("[TEAM_ID]") +val json = response.body?.string() \ No newline at end of file diff --git a/app/sdks/client-android/docs/examples/teams/get-memberships.md b/app/sdks/client-android/docs/examples/teams/get-memberships.md new file mode 100644 index 0000000000..32db0b8809 --- /dev/null +++ b/app/sdks/client-android/docs/examples/teams/get-memberships.md @@ -0,0 +1,10 @@ +import io.appwrite.Client +import io.appwrite.services.Teams + +val client = Client(context) + .setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint + .setProject("5df5acd0d48c2") // Your project ID + +val teamsService = Teams(client) +val response = teamsService.getMemberships("[TEAM_ID]") +val json = response.body?.string() \ No newline at end of file diff --git a/app/sdks/client-android/docs/examples/teams/get.md b/app/sdks/client-android/docs/examples/teams/get.md new file mode 100644 index 0000000000..eb2451f66c --- /dev/null +++ b/app/sdks/client-android/docs/examples/teams/get.md @@ -0,0 +1,10 @@ +import io.appwrite.Client +import io.appwrite.services.Teams + +val client = Client(context) + .setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint + .setProject("5df5acd0d48c2") // Your project ID + +val teamsService = Teams(client) +val response = teamsService.get("[TEAM_ID]") +val json = response.body?.string() \ No newline at end of file diff --git a/app/sdks/client-android/docs/examples/teams/list.md b/app/sdks/client-android/docs/examples/teams/list.md new file mode 100644 index 0000000000..5da9925e48 --- /dev/null +++ b/app/sdks/client-android/docs/examples/teams/list.md @@ -0,0 +1,10 @@ +import io.appwrite.Client +import io.appwrite.services.Teams + +val client = Client(context) + .setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint + .setProject("5df5acd0d48c2") // Your project ID + +val teamsService = Teams(client) +val response = teamsService.list() +val json = response.body?.string() \ No newline at end of file diff --git a/app/sdks/client-android/docs/examples/teams/update-membership-roles.md b/app/sdks/client-android/docs/examples/teams/update-membership-roles.md new file mode 100644 index 0000000000..17904fa125 --- /dev/null +++ b/app/sdks/client-android/docs/examples/teams/update-membership-roles.md @@ -0,0 +1,10 @@ +import io.appwrite.Client +import io.appwrite.services.Teams + +val client = Client(context) + .setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint + .setProject("5df5acd0d48c2") // Your project ID + +val teamsService = Teams(client) +val response = teamsService.updateMembershipRoles("[TEAM_ID]", "[MEMBERSHIP_ID]", List()) +val json = response.body?.string() \ No newline at end of file diff --git a/app/sdks/client-android/docs/examples/teams/update-membership-status.md b/app/sdks/client-android/docs/examples/teams/update-membership-status.md new file mode 100644 index 0000000000..dca6a1ba14 --- /dev/null +++ b/app/sdks/client-android/docs/examples/teams/update-membership-status.md @@ -0,0 +1,10 @@ +import io.appwrite.Client +import io.appwrite.services.Teams + +val client = Client(context) + .setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint + .setProject("5df5acd0d48c2") // Your project ID + +val teamsService = Teams(client) +val response = teamsService.updateMembershipStatus("[TEAM_ID]", "[MEMBERSHIP_ID]", "[USER_ID]", "[SECRET]") +val json = response.body?.string() \ No newline at end of file diff --git a/app/sdks/client-android/docs/examples/teams/update.md b/app/sdks/client-android/docs/examples/teams/update.md new file mode 100644 index 0000000000..ab4cfcc39a --- /dev/null +++ b/app/sdks/client-android/docs/examples/teams/update.md @@ -0,0 +1,10 @@ +import io.appwrite.Client +import io.appwrite.services.Teams + +val client = Client(context) + .setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint + .setProject("5df5acd0d48c2") // Your project ID + +val teamsService = Teams(client) +val response = teamsService.update("[TEAM_ID]", "[NAME]") +val json = response.body?.string() \ No newline at end of file diff --git a/app/sdks/client-android/example/.gitignore b/app/sdks/client-android/example/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/app/sdks/client-android/example/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/sdks/client-android/example/build.gradle b/app/sdks/client-android/example/build.gradle new file mode 100644 index 0000000000..6057427c47 --- /dev/null +++ b/app/sdks/client-android/example/build.gradle @@ -0,0 +1,59 @@ +plugins { + id 'com.android.application' + id 'kotlin-android' +} + +android { + compileSdkVersion 30 + buildToolsVersion "30.0.3" + + defaultConfig { + applicationId "io.appwrite.android" + minSdkVersion 21 + targetSdkVersion 30 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildFeatures { + dataBinding true + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = '1.8' + } +} + +dependencies { + + implementation project(path: ':library') + implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + implementation 'androidx.core:core-ktx:1.5.0' + implementation 'androidx.appcompat:appcompat:1.3.0' + implementation 'com.google.android.material:material:1.3.0' + implementation 'androidx.constraintlayout:constraintlayout:2.0.4' + implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5' + implementation "androidx.fragment:fragment-ktx:1.3.2" + implementation 'androidx.navigation:navigation-ui-ktx:2.3.5' + implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.1' + implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1' + implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5' + implementation 'androidx.navigation:navigation-ui-ktx:2.3.5' + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.3" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.3" + testImplementation 'junit:junit:4.+' + androidTestImplementation 'androidx.test.ext:junit:1.1.2' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' +} \ No newline at end of file diff --git a/app/sdks/client-android/example/src/main/AndroidManifest.xml b/app/sdks/client-android/example/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..4b65549deb --- /dev/null +++ b/app/sdks/client-android/example/src/main/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/sdks/client-android/example/src/main/java/io/appwrite/android/MainActivity.kt b/app/sdks/client-android/example/src/main/java/io/appwrite/android/MainActivity.kt new file mode 100644 index 0000000000..2fe5ae9ce9 --- /dev/null +++ b/app/sdks/client-android/example/src/main/java/io/appwrite/android/MainActivity.kt @@ -0,0 +1,23 @@ +package io.appwrite.android + +import androidx.appcompat.app.AppCompatActivity +import android.os.Bundle +import androidx.fragment.app.add +import androidx.fragment.app.commit +import io.appwrite.android.ui.accounts.AccountsFragment +import io.appwrite.android.utils.Client + +class MainActivity : AppCompatActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + Client.create(applicationContext) + + if (savedInstanceState == null) { + supportFragmentManager.commit { + setReorderingAllowed(true) + add(R.id.fragment_container_view) + } + } + } +} \ No newline at end of file diff --git a/app/sdks/client-android/example/src/main/java/io/appwrite/android/ui/accounts/AccountsFragment.kt b/app/sdks/client-android/example/src/main/java/io/appwrite/android/ui/accounts/AccountsFragment.kt new file mode 100644 index 0000000000..746cb7e8f5 --- /dev/null +++ b/app/sdks/client-android/example/src/main/java/io/appwrite/android/ui/accounts/AccountsFragment.kt @@ -0,0 +1,69 @@ +package io.appwrite.android.ui.accounts + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Toast +import androidx.activity.ComponentActivity +import androidx.databinding.DataBindingUtil +import androidx.fragment.app.Fragment +import androidx.lifecycle.Observer +import androidx.lifecycle.ViewModelProvider +import io.appwrite.android.R +import io.appwrite.android.databinding.FragmentAccountBinding + + +class AccountsFragment : Fragment() { + + private lateinit var binding: FragmentAccountBinding + private lateinit var viewModel: AccountsViewModel + + override fun onCreateView( + inflater: LayoutInflater , + container: ViewGroup? , + savedInstanceState: Bundle? + ): View? { + viewModel = ViewModelProvider(this).get(AccountsViewModel::class.java) + binding = DataBindingUtil.inflate( + inflater, + R.layout.fragment_account, + container, + false + ) + binding.lifecycleOwner = viewLifecycleOwner + binding.login.setOnClickListener{ + viewModel.onLogin(binding.email.text, binding.password.text) + } + + binding.signup.setOnClickListener{ + viewModel.onSignup(binding.email.text, binding.password.text, binding.name.text) + } + + binding.getUser.setOnClickListener{ + viewModel.getUser() + } + + binding.oAuth.setOnClickListener{ + viewModel.oAuthLogin(activity as ComponentActivity) + } + + binding.logout.setOnClickListener{ + viewModel.logout() + } + + viewModel.error.observe(viewLifecycleOwner, Observer { event -> + event?.getContentIfNotHandled()?.let { // Only proceed if the event has never been handled + Toast.makeText(requireContext(), it.message , Toast.LENGTH_SHORT).show() + } + }) + + viewModel.response.observe(viewLifecycleOwner, Observer { event -> + event?.getContentIfNotHandled()?.let { + binding.responseTV.setText(it) + } + }) + + return binding.root + } +} \ No newline at end of file diff --git a/app/sdks/client-android/example/src/main/java/io/appwrite/android/ui/accounts/AccountsViewModel.kt b/app/sdks/client-android/example/src/main/java/io/appwrite/android/ui/accounts/AccountsViewModel.kt new file mode 100644 index 0000000000..2d812e8c02 --- /dev/null +++ b/app/sdks/client-android/example/src/main/java/io/appwrite/android/ui/accounts/AccountsViewModel.kt @@ -0,0 +1,96 @@ +package io.appwrite.android.ui.accounts + +import android.text.Editable +import androidx.activity.ComponentActivity +import androidx.lifecycle.* +import io.appwrite.android.utils.Client.client +import io.appwrite.android.utils.Event +import io.appwrite.exceptions.AppwriteException +import io.appwrite.services.Account +import kotlinx.coroutines.launch +import org.json.JSONObject + + +class AccountsViewModel : ViewModel() { + + private val _error = MutableLiveData>().apply { + value = null + } + val error: LiveData> = _error + + private val _response = MutableLiveData>().apply { + value = null + } + val response: LiveData> = _response + + private val accountService by lazy { + Account(client) + } + + fun onLogin(email: Editable , password : Editable) { + viewModelScope.launch { + try { + var response = accountService.createSession(email.toString(), password.toString()) + var json = response.body?.string() ?: "" + json = JSONObject(json).toString(8) + _response.postValue(Event(json)) + } catch (e: AppwriteException) { + _error.postValue(Event(e)) + } + } + + } + + fun onSignup(email: Editable , password : Editable, name: Editable) { + viewModelScope.launch { + try { + var response = accountService.create(email.toString(), password.toString(), name.toString()) + var json = response.body?.string() ?: "" + json = JSONObject(json).toString(2) + _response.postValue(Event(json)) + } catch (e: AppwriteException) { + _error.postValue(Event(e)) + } + } + + } + + fun oAuthLogin(activity: ComponentActivity) { + viewModelScope.launch { + try { + accountService.createOAuth2Session(activity, "facebook", "appwrite-callback-6070749e6acd4://demo.appwrite.io/auth/oauth2/success", "appwrite-callback-6070749e6acd4://demo.appwrite.io/auth/oauth2/failure") + } catch (e: Exception) { + _error.postValue(Event(e)) + } catch (e: AppwriteException) { + _error.postValue(Event(e)) + } + } + } + + fun getUser() { + viewModelScope.launch { + try { + var response = accountService.get() + var json = response.body?.string() ?: "" + json = JSONObject(json).toString(2) + _response.postValue(Event(json)) + } catch (e: AppwriteException) { + _error.postValue(Event(e)) + } + } + } + + fun logout() { + viewModelScope.launch { + try { + var response = accountService.deleteSession("current") + var json = response.body?.string()?.ifEmpty { "{}" } + json = JSONObject(json).toString(4) + _response.postValue(Event(json)) + } catch (e: AppwriteException) { + _error.postValue(Event(e)) + } + } + } + +} \ No newline at end of file diff --git a/app/sdks/client-android/example/src/main/java/io/appwrite/android/utils/Client.kt b/app/sdks/client-android/example/src/main/java/io/appwrite/android/utils/Client.kt new file mode 100644 index 0000000000..66ce68191e --- /dev/null +++ b/app/sdks/client-android/example/src/main/java/io/appwrite/android/utils/Client.kt @@ -0,0 +1,20 @@ +package io.appwrite.android.utils + +import android.content.Context +import io.appwrite.Client + +object Client { + lateinit var client : Client + + fun create(context: Context) { + client = Client(context) + .setEndpoint("https://demo.appwrite.io/v1") + .setProject("6070749e6acd4") + + /* Useful when testing locally */ +// client = Client(context) +// .setEndpoint("https://192.168.1.35/v1") +// .setProject("60bdbc911784e") +// .setSelfSigned(true) + } +} \ No newline at end of file diff --git a/app/sdks/client-android/example/src/main/java/io/appwrite/android/utils/Event.kt b/app/sdks/client-android/example/src/main/java/io/appwrite/android/utils/Event.kt new file mode 100644 index 0000000000..a5224794eb --- /dev/null +++ b/app/sdks/client-android/example/src/main/java/io/appwrite/android/utils/Event.kt @@ -0,0 +1,27 @@ +package io.appwrite.android.utils + +/** + * Used as a wrapper for data that is exposed via a LiveData that represents an event. + */ +open class Event(private val content: T) { + + var hasBeenHandled = false + private set // Allow external read but not write + + /** + * Returns the content and prevents its use again. + */ + fun getContentIfNotHandled(): T? { + return if (hasBeenHandled) { + null + } else { + hasBeenHandled = true + content + } + } + + /** + * Returns the content, even if it's already been handled. + */ + fun peekContent(): T = content +} diff --git a/app/sdks/client-android/example/src/main/res/drawable/ic_launcher_background.xml b/app/sdks/client-android/example/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000000..07d5da9cbf --- /dev/null +++ b/app/sdks/client-android/example/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/sdks/client-android/example/src/main/res/drawable/ic_launcher_foreground.xml b/app/sdks/client-android/example/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000000..2b068d1146 --- /dev/null +++ b/app/sdks/client-android/example/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/sdks/client-android/example/src/main/res/layout/activity_main.xml b/app/sdks/client-android/example/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000000..7aa7dd7f48 --- /dev/null +++ b/app/sdks/client-android/example/src/main/res/layout/activity_main.xml @@ -0,0 +1,15 @@ + + + + + + \ No newline at end of file diff --git a/app/sdks/client-android/example/src/main/res/layout/fragment_account.xml b/app/sdks/client-android/example/src/main/res/layout/fragment_account.xml new file mode 100644 index 0000000000..2fb34c9578 --- /dev/null +++ b/app/sdks/client-android/example/src/main/res/layout/fragment_account.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + -

    Register your Android App

    +

    Register your Android App

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