From 20fa8b30175a01382dc6b86cc745566f34314a58 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Wed, 18 Sep 2024 14:29:44 +0100 Subject: [PATCH 01/33] fix: flaky functions shot 1 --- .../e2e/Services/Functions/FunctionsBase.php | 25 +++-- .../Functions/FunctionsCustomClientTest.php | 89 ++++++++--------- .../Functions/FunctionsCustomServerTest.php | 98 +++++++++---------- tests/extensions/Async.php | 17 ++++ tests/extensions/Async/Eventually.php | 54 ++++++++++ 5 files changed, 172 insertions(+), 111 deletions(-) create mode 100644 tests/extensions/Async.php create mode 100644 tests/extensions/Async/Eventually.php diff --git a/tests/e2e/Services/Functions/FunctionsBase.php b/tests/e2e/Services/Functions/FunctionsBase.php index 2d94b9f0e3..26f2ae7aed 100644 --- a/tests/e2e/Services/Functions/FunctionsBase.php +++ b/tests/e2e/Services/Functions/FunctionsBase.php @@ -2,11 +2,14 @@ namespace Tests\E2E\Services\Functions; +use Appwrite\Tests\Async; use Tests\E2E\Client; use Utopia\CLI\Console; trait FunctionsBase { + use Async; + protected string $stdout = ''; protected string $stderr = ''; @@ -15,29 +18,23 @@ trait FunctionsBase Console::execute('cd ' . realpath(__DIR__ . "/../../../resources/functions") . "/$folder && tar --exclude code.tar.gz -czf code.tar.gz .", '', $this->stdout, $this->stderr); } - protected function awaitDeploymentIsBuilt($functionId, $deploymentId, $checkForSuccess = true): void + protected function assertDeployment($functionId, $deploymentId, $allowFailure = false) { - while (true) { + $this->assertEventually(function () use ($functionId, $deploymentId, $allowFailure) { $deployment = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/deployments/' . $deploymentId, [ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'], ]); - if ( - $deployment['headers']['status-code'] >= 400 - || \in_array($deployment['body']['status'], ['ready', 'failed']) - ) { - break; + $status = $deployment['body']['status']; + + if ($allowFailure && $status === 'failed') { + $this->fail('Deployment failed: ' . json_encode($deployment['body']['buildStderr'])); } - \sleep(1); - } - - if ($checkForSuccess) { - $this->assertEquals(200, $deployment['headers']['status-code']); - $this->assertEquals('ready', $deployment['body']['status'], \json_encode($deployment['body'])); - } + $this->assertEquals('ready', $status); + }, 180000, 3000); } // /** diff --git a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php index 92b7c33034..f5f15a0fc6 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php @@ -2,7 +2,6 @@ namespace Tests\E2E\Services\Functions; -use Appwrite\Tests\Retry; use CURLFile; use Tests\E2E\Client; use Tests\E2E\Scopes\ProjectCustom; @@ -44,7 +43,6 @@ class FunctionsCustomClientTest extends Scope return []; } - #[Retry(count: 2)] public function testCreateExecution(): array { /** @@ -120,7 +118,7 @@ class FunctionsCustomClientTest extends Scope $this->assertEquals(202, $deployment['headers']['status-code']); - $this->awaitDeploymentIsBuilt($function['body']['$id'], $deploymentId); + $this->assertDeployment($function['body']['$id'], $deploymentId); $function = $this->client->call(Client::METHOD_PATCH, '/functions/' . $function['body']['$id'] . '/deployments/' . $deploymentId, [ 'content-type' => 'application/json', @@ -148,25 +146,26 @@ class FunctionsCustomClientTest extends Scope $this->assertEquals(202, $execution['headers']['status-code']); - // Wait for the first scheduled execution to be created - sleep(90); + $this->assertEventually(function () use ($function) { + $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $function['body']['$id'] . '/executions', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]); - $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $function['body']['$id'] . '/executions', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ]); + $this->assertEquals(200, $executions['headers']['status-code']); + $this->assertCount(2, $executions['body']['executions']); - $this->assertEquals(200, $executions['headers']['status-code']); - $this->assertCount(2, $executions['body']['executions']); - $this->assertIsArray($executions['body']['executions']); - $this->assertEquals($executions['body']['executions'][1]['trigger'], 'schedule'); - $this->assertEquals($executions['body']['executions'][1]['status'], 'completed'); - $this->assertEquals($executions['body']['executions'][1]['responseStatusCode'], 200); - $this->assertEquals($executions['body']['executions'][1]['responseBody'], ''); - $this->assertNotEmpty($executions['body']['executions'][1]['logs'], ''); - $this->assertNotEmpty($executions['body']['executions'][1]['errors'], ''); - $this->assertGreaterThan(0, $executions['body']['executions'][1]['duration']); + // Check if the scheduled execution has completed + $scheduledExecution = $executions['body']['executions'][1]; + $this->assertEquals('schedule', $scheduledExecution['trigger']); + $this->assertEquals('completed', $scheduledExecution['status']); + $this->assertEquals(200, $scheduledExecution['responseStatusCode']); + $this->assertEquals('', $scheduledExecution['responseBody']); + $this->assertNotEmpty($scheduledExecution['logs']); + $this->assertNotEmpty($scheduledExecution['errors']); + $this->assertGreaterThan(0, $scheduledExecution['duration']); + }, 120000, 2000); // Cleanup : Delete function $response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $function['body']['$id'], [ @@ -216,7 +215,7 @@ class FunctionsCustomClientTest extends Scope $deploymentId = $deployment['body']['$id'] ?? ''; $this->assertEquals(202, $deployment['headers']['status-code']); - $this->awaitDeploymentIsBuilt($function['body']['$id'], $deploymentId, true); + $this->assertDeployment($function['body']['$id'], $deploymentId, true); // Schedule execution for the future \date_default_timezone_set('UTC'); @@ -245,25 +244,22 @@ class FunctionsCustomClientTest extends Scope $executionId = $execution['body']['$id']; - $start = \microtime(true); - while (true) { + $this->assertEventually(function () use ($function, $executionId) { $execution = $this->client->call(Client::METHOD_GET, '/functions/' . $function['body']['$id'] . '/executions/' . $executionId, [ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'], ]); - if ($execution['body']['status'] === 'completed') { - break; - } + $this->assertEquals('completed', $execution['body']['status']); + }, 150000, 2000); // Timeout after 150 seconds, wait 2 seconds between retries - $timeout = 60 + 60 + 15; // up to 1 minute round up, 1 minute schedule postpone, 15s cold start safety - if (\microtime(true) - $start > $timeout) { - $this->fail('Scheduled execution did not complete with status ' . $execution['body']['status'] . ': ' . \json_encode($execution)); - } - - usleep(500000); // 0.5 seconds - } + // After ensuring the execution is completed, fetch it again for assertions + $execution = $this->client->call(Client::METHOD_GET, '/functions/' . $function['body']['$id'] . '/executions/' . $executionId, [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]); $this->assertEquals(200, $execution['headers']['status-code']); $this->assertEquals(200, $execution['body']['responseStatusCode']); @@ -409,7 +405,7 @@ class FunctionsCustomClientTest extends Scope $deploymentId = $deployment['body']['$id'] ?? ''; - $this->awaitDeploymentIsBuilt($function['body']['$id'], $deploymentId); + $this->assertDeployment($function['body']['$id'], $deploymentId); $function = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, [ 'content-type' => 'application/json', @@ -458,17 +454,16 @@ class FunctionsCustomClientTest extends Scope $executionId = $execution['body']['$id'] ?? ''; - sleep(5); + $this->assertEventually(function () use ($functionId, $executionId, $projectId, $apikey) { + $execution = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions/' . $executionId, [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + 'x-appwrite-key' => $apikey, + ]); - $execution = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions/' . $executionId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $projectId, - 'x-appwrite-key' => $apikey, - ]); - - $this->assertEmpty($execution['body']['responseBody']); - $this->assertEquals(200, $execution['headers']['status-code']); - $this->assertEquals(200, $execution['body']['responseStatusCode']); + $this->assertEquals('completed', $execution['body']['status']); + $this->assertEquals(200, $execution['body']['responseStatusCode']); + }, 30000, 1000); return [ 'functionId' => $functionId @@ -521,7 +516,7 @@ class FunctionsCustomClientTest extends Scope $deploymentId = $deployment['body']['$id'] ?? ''; - $this->awaitDeploymentIsBuilt($function['body']['$id'], $deploymentId); + $this->assertDeployment($function['body']['$id'], $deploymentId); // Why do we have to do this? $function = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, [ @@ -760,7 +755,7 @@ class FunctionsCustomClientTest extends Scope $this->assertEquals(202, $deployment['headers']['status-code']); - $this->awaitDeploymentIsBuilt($function['body']['$id'], $deploymentId); + $this->assertDeployment($function['body']['$id'], $deploymentId); $function = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, [ 'content-type' => 'application/json', @@ -853,7 +848,7 @@ class FunctionsCustomClientTest extends Scope $this->assertEquals(202, $deployment['headers']['status-code']); - $this->awaitDeploymentIsBuilt($function['body']['$id'], $deploymentId); + $this->assertDeployment($function['body']['$id'], $deploymentId); $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([ 'content-type' => 'application/json', diff --git a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php index 2958e6cb5f..4005279560 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php @@ -3,7 +3,6 @@ namespace Tests\E2E\Services\Functions; use Appwrite\Functions\Specification; -use Appwrite\Tests\Retry; use CURLFile; use PHPUnit\Framework\ExpectationFailedException; use Tests\E2E\Client; @@ -429,7 +428,7 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals(202, $deployment['headers']['status-code']); - $this->awaitDeploymentIsBuilt($function['body']['$id'], $deploymentId); + $this->assertDeployment($function['body']['$id'], $deploymentId); $functionDetails = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/deployments/' . $deploymentId, [ 'content-type' => 'application/json', @@ -521,7 +520,7 @@ class FunctionsCustomServerTest extends Scope // Wait for deployment build to finish // Deployment is automatically activated - $this->awaitDeploymentIsBuilt($function['body']['$id'], $deploymentId); + $this->assertDeployment($function['body']['$id'], $deploymentId); $deployments = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/deployments/' . $deploymentId, [ 'content-type' => 'application/json', @@ -635,7 +634,7 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals(true, (new DatetimeValidator())->isValid($deployment['body']['$createdAt'])); $this->assertEquals('index.php', $deployment['body']['entrypoint']); - $this->awaitDeploymentIsBuilt($data['functionId'], $deploymentId); + $this->assertDeployment($data['functionId'], $deploymentId); $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $data['functionId'] . '/deployments', array_merge([ 'content-type' => 'multipart/form-data', @@ -650,7 +649,7 @@ class FunctionsCustomServerTest extends Scope $deploymentIdInactive = $deployment['body']['$id']; - $this->awaitDeploymentIsBuilt($data['functionId'], $deploymentIdInactive); + $this->assertDeployment($data['functionId'], $deploymentIdInactive); $function = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'], array_merge([ 'content-type' => 'application/json', @@ -696,26 +695,16 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals(true, (new DatetimeValidator())->isValid($deployment['body']['$createdAt'])); $this->assertEquals('index.php', $deployment['body']['entrypoint']); - // Poll until deployment is in progress - while (true) { + $this->assertEventually(function () use ($data, $deploymentId) { $deployment = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/deployments/' . $deploymentId, [ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'], ]); - if ( - $deployment['headers']['status-code'] >= 400 - || $deployment['body']['status'] === 'building' - ) { - break; - } - - \sleep(1); - } - - $this->assertEquals(200, $deployment['headers']['status-code']); - $this->assertEquals('building', $deployment['body']['status']); + $this->assertEquals(200, $deployment['headers']['status-code']); + $this->assertEquals('building', $deployment['body']['status']); + }, 60000, 1000); // Cancel the deployment build $cancel = $this->client->call(Client::METHOD_PATCH, '/functions/' . $data['functionId'] . '/deployments/' . $deploymentId . '/build', [ @@ -729,10 +718,10 @@ class FunctionsCustomServerTest extends Scope /** * Build worker still runs the build. - * 30s sleep gives worker enough time to finish build. + * 10s sleep gives worker enough time to finish build. * After build finished, it should still be canceled, not ready. */ - \sleep(30); + \sleep(10); $deployment = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/deployments/' . $deploymentId, [ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -794,7 +783,7 @@ class FunctionsCustomServerTest extends Scope $deploymentId = $largeTag['body']['$id']; - $this->awaitDeploymentIsBuilt($data['functionId'], $deploymentId, true); + $this->assertDeployment($data['functionId'], $deploymentId, true); $response = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/deployments/' . $deploymentId, array_merge([ 'content-type' => 'application/json', @@ -1294,7 +1283,6 @@ class FunctionsCustomServerTest extends Scope /** * @depends testUpdateDeployment */ - #[Retry(count: 2)] public function testSyncCreateExecution($data): array { /** @@ -1417,14 +1405,23 @@ class FunctionsCustomServerTest extends Scope $executionId = $execution['body']['$id'] ?? ''; $this->assertEquals(202, $execution['headers']['status-code']); - sleep(5); - $execution = $this->client->call(Client::METHOD_DELETE, '/functions/' . $data['functionId'] . '/executions/' . $executionId, array_merge([ + + $this->assertEventually(function () use ($data, $executionId) { + $execution = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/executions/' . $executionId, [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ]); + + $this->assertEquals(200, $execution['headers']['status-code']); + $this->assertEquals('waiting', $execution['body']['status']); + }, 10000, 500); + + $execution = $this->client->call(Client::METHOD_DELETE, '/functions/' . $data['functionId'] . '/executions/' . $executionId, [ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); + ]); $this->assertEquals(204, $execution['headers']['status-code']); - $this->assertEmpty($execution['body']); return $data; } @@ -1432,7 +1429,6 @@ class FunctionsCustomServerTest extends Scope /** * @depends testGetExecution */ - #[Retry(count: 2)] public function testUpdateSpecs($data): array { /** @@ -1622,7 +1618,7 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals(202, $deployment['headers']['status-code']); - $this->awaitDeploymentIsBuilt($functionId, $deploymentId); + $this->assertDeployment($functionId, $deploymentId); $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([ 'content-type' => 'application/json', @@ -1773,7 +1769,7 @@ class FunctionsCustomServerTest extends Scope $deploymentId = $deployment['body']['$id'] ?? ''; $this->assertEquals(202, $deployment['headers']['status-code']); - $this->awaitDeploymentIsBuilt($function['body']['$id'], $deploymentId, checkForSuccess: false); + $this->assertDeployment($function['body']['$id'], $deploymentId, allowFailure: true); $deployment = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, array_merge([ 'content-type' => 'application/json', @@ -1868,7 +1864,7 @@ class FunctionsCustomServerTest extends Scope $deploymentId = $deployment['body']['$id'] ?? ''; $this->assertEquals(202, $deployment['headers']['status-code']); - $this->awaitDeploymentIsBuilt($function['body']['$id'], $deploymentId, checkForSuccess: false); + $this->assertDeployment($function['body']['$id'], $deploymentId, allowFailure: true); $deployment = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, array_merge([ 'content-type' => 'application/json', @@ -1954,7 +1950,7 @@ class FunctionsCustomServerTest extends Scope $deploymentId = $deployment['body']['$id'] ?? ''; $this->assertEquals(202, $deployment['headers']['status-code']); - $this->awaitDeploymentIsBuilt($function['body']['$id'], $deploymentId, checkForSuccess: false); + $this->assertDeployment($function['body']['$id'], $deploymentId, allowFailure: true); $deployment = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, array_merge([ 'content-type' => 'application/json', @@ -2054,7 +2050,7 @@ class FunctionsCustomServerTest extends Scope $deploymentId = $deployment['body']['$id'] ?? ''; $this->assertEquals(202, $deployment['headers']['status-code']); - $this->awaitDeploymentIsBuilt($function['body']['$id'], $deploymentId, checkForSuccess: false); + $this->assertDeployment($function['body']['$id'], $deploymentId, allowFailure: true); $deployment = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, array_merge([ 'content-type' => 'application/json', @@ -2147,7 +2143,7 @@ class FunctionsCustomServerTest extends Scope $deploymentId = $deployment['body']['$id'] ?? ''; $this->assertEquals(202, $deployment['headers']['status-code']); - $this->awaitDeploymentIsBuilt($function['body']['$id'], $deploymentId, checkForSuccess: false); + $this->assertDeployment($function['body']['$id'], $deploymentId, allowFailure: true); $deployment = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, array_merge([ 'content-type' => 'application/json', @@ -2169,21 +2165,23 @@ class FunctionsCustomServerTest extends Scope ]); $userId = $user['body']['$id']; - $this->assertEquals(201, $user['headers']['status-code']); - // Wait for execution to occur - sleep(15); + $execution = null; + $this->assertEventually(function () use ($functionId, &$execution) { + $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ]); - $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); + if (empty($executions['body']['executions'])) { + throw new \Exception('No executions found yet'); + } - $execution = $executions['body']['executions'][0]; + $execution = $executions['body']['executions'][0]; + $this->assertEquals('completed', $execution['status']); + }, 30000, 1000); - $this->assertEquals(200, $executions['headers']['status-code']); - $this->assertEquals('completed', $execution['status']); $this->assertEquals(204, $execution['responseStatusCode']); $this->assertStringContainsString($userId, $execution['logs']); $this->assertStringContainsString('Event User', $execution['logs']); @@ -2243,7 +2241,7 @@ class FunctionsCustomServerTest extends Scope $deploymentId = $deployment['body']['$id'] ?? ''; $this->assertEquals(202, $deployment['headers']['status-code']); - $this->awaitDeploymentIsBuilt($function['body']['$id'], $deploymentId, checkForSuccess: false); + $this->assertDeployment($function['body']['$id'], $deploymentId, allowFailure: true); $deployment = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/deployments/' . $deploymentId, array_merge([ 'content-type' => 'application/json', @@ -2346,7 +2344,7 @@ class FunctionsCustomServerTest extends Scope $deploymentId = $deployment['body']['$id'] ?? ''; $this->assertEquals(202, $deployment['headers']['status-code']); - $this->awaitDeploymentIsBuilt($function['body']['$id'], $deploymentId, checkForSuccess: false); + $this->assertDeployment($function['body']['$id'], $deploymentId, allowFailure: true); $deployment = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, array_merge([ 'content-type' => 'application/json', @@ -2437,7 +2435,7 @@ class FunctionsCustomServerTest extends Scope $deploymentId = $deployment['body']['$id'] ?? ''; $this->assertEquals(202, $deployment['headers']['status-code']); - $this->awaitDeploymentIsBuilt($function['body']['$id'], $deploymentId, checkForSuccess: false); + $this->assertDeployment($function['body']['$id'], $deploymentId, allowFailure: true); $deployment = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, array_merge([ 'content-type' => 'application/json', @@ -2553,7 +2551,7 @@ class FunctionsCustomServerTest extends Scope $deploymentId = $deployment['body']['$id'] ?? ''; $this->assertEquals(202, $deployment['headers']['status-code']); - $this->awaitDeploymentIsBuilt($function['body']['$id'], $deploymentId, checkForSuccess: false); + $this->assertDeployment($function['body']['$id'], $deploymentId, allowFailure: true); $deployment = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, array_merge([ 'content-type' => 'application/json', @@ -2639,7 +2637,7 @@ class FunctionsCustomServerTest extends Scope $deploymentId = $deployment['body']['$id'] ?? ''; $this->assertEquals(202, $deployment['headers']['status-code']); - $this->awaitDeploymentIsBuilt($function['body']['$id'], $deploymentId, checkForSuccess: false); + $this->assertDeployment($function['body']['$id'], $deploymentId, allowFailure: true); $deployment = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, array_merge([ 'content-type' => 'application/json', @@ -2735,7 +2733,7 @@ class FunctionsCustomServerTest extends Scope $deploymentId = $deployment['body']['$id'] ?? ''; - $this->awaitDeploymentIsBuilt($functionId, $deploymentId, checkForSuccess: false); + $this->assertDeployment($functionId, $deploymentId, allowFailure: true); // Sync Executions test $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([ diff --git a/tests/extensions/Async.php b/tests/extensions/Async.php new file mode 100644 index 0000000000..48af24bc02 --- /dev/null +++ b/tests/extensions/Async.php @@ -0,0 +1,17 @@ +timeoutMs = $timeoutMs; + $this->waitMs = $waitMs; + } + + public function evaluate(mixed $probe, string $description = '', bool $returnResult = false): ?bool + { + if (!is_callable($probe)) { + throw new \Exception('Probe must be a callable'); + } + + $start = microtime(true); + $lastException = null; + + do { + try { + $probe(); + return true; + } catch (\Exception $exception) { + $lastException = $exception; + } + + usleep($this->waitMs * 1000); + } while (microtime(true) - $start < $this->timeoutMs / 1000); + + if ($returnResult) { + return false; + } + + throw $lastException; + } + + protected function failureDescription(mixed $other): string + { + return 'the given probe was satisfied within ' . $this->timeoutMs . 'ms.'; + } + + public function toString(): string + { + return 'Eventually'; + } +} From 121b9f32b816b26d3a4afb49a1e0a57e8f8d6be2 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Wed, 18 Sep 2024 14:50:36 +0100 Subject: [PATCH 02/33] fix: allowFailure --- .../e2e/Services/Functions/FunctionsBase.php | 2 +- .../Functions/FunctionsCustomServerTest.php | 177 ++++-------------- 2 files changed, 40 insertions(+), 139 deletions(-) diff --git a/tests/e2e/Services/Functions/FunctionsBase.php b/tests/e2e/Services/Functions/FunctionsBase.php index 26f2ae7aed..7f33e0b2ee 100644 --- a/tests/e2e/Services/Functions/FunctionsBase.php +++ b/tests/e2e/Services/Functions/FunctionsBase.php @@ -34,7 +34,7 @@ trait FunctionsBase } $this->assertEquals('ready', $status); - }, 180000, 3000); + }, 180000, 1000); } // /** diff --git a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php index 4005279560..193e99d89e 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php @@ -1631,33 +1631,32 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals(202, $execution['headers']['status-code']); - sleep(20); + $executionData = null; - $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'queries' => [ - Query::equal('trigger', ['http'])->toString(), - ], - ]); + $this->assertEventually(function () use ($functionId, $executionId, &$executionData) { + $executionData = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions/' . $executionId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); - $this->assertEquals($executions['headers']['status-code'], 200); - $this->assertEquals($executions['body']['total'], 1); - $this->assertIsArray($executions['body']['executions']); - $this->assertCount(1, $executions['body']['executions']); - $this->assertEquals($executions['body']['executions'][0]['$id'], $executionId); - $this->assertEquals($executions['body']['executions'][0]['trigger'], 'http'); - $this->assertEquals($executions['body']['executions'][0]['status'], 'failed'); - $this->assertEquals($executions['body']['executions'][0]['responseStatusCode'], 500); - $this->assertGreaterThan(2, $executions['body']['executions'][0]['duration']); - $this->assertLessThan(20, $executions['body']['executions'][0]['duration']); - $this->assertEquals($executions['body']['executions'][0]['responseBody'], ''); - $this->assertEquals($executions['body']['executions'][0]['logs'], ''); - $this->assertStringContainsString('timed out', $executions['body']['executions'][0]['errors']); + $this->assertEquals(200, $executionData['headers']['status-code']); + $this->assertEquals('failed', $executionData['body']['status']); - $start = \microtime(true); - while (true) { + if ($executionData['body']['status'] !== 'failed') { + } + }, 30000, 1000); + + $this->assertEquals('failed', $executionData['body']['status']); + $this->assertEquals(500, $executionData['body']['responseStatusCode']); + $this->assertGreaterThan(2, $executionData['body']['duration']); + $this->assertLessThan(20, $executionData['body']['duration']); + $this->assertEquals('', $executionData['body']['responseBody']); + $this->assertEquals('', $executionData['body']['logs']); + $this->assertStringContainsString('timed out', $executionData['body']['errors']); + + $scheduledExecutionData = null; + + $this->assertEventually(function () use ($functionId, &$scheduledExecutionData) { $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -1668,27 +1667,12 @@ class FunctionsCustomServerTest extends Scope ]); $this->assertEquals(200, $executions['headers']['status-code']); + $this->assertEquals(1, $executions['body']['total']); - if (\count($executions['body']['executions']) > 0) { - break; - } + $scheduledExecutionData = $executions['body']['executions'][0]; + }, 140000, 1000); // Wait up to 140 seconds, checking every 1 second - // 0s would mean instant execution - // +60 seconds, maximum possible waiting time before next minute - // +10 seconds, maximum update interval time - // +60 seconds, possible overlap between update and schedule tick - // +10 seconds, maximum execution time including cold-start - // Result: We allow maximum - if (\microtime(true) - $start > 140) { - $this->fail('Execution did not create within 140 seconds of schedule: ' . \json_encode($executions)); - } - - usleep(1000000); // 1 second - } - - $this->assertEquals(200, $executions['headers']['status-code']); - $this->assertGreaterThanOrEqual(1, \count($executions['body']['executions'])); - $this->assertEquals($executions['body']['executions'][0]['trigger'], 'schedule'); + $this->assertEquals('schedule', $scheduledExecutionData['trigger']); // Cleanup : Delete function $response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, [ @@ -1700,6 +1684,7 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals(204, $response['headers']['status-code']); } + /** * * @return array @@ -1769,14 +1754,7 @@ class FunctionsCustomServerTest extends Scope $deploymentId = $deployment['body']['$id'] ?? ''; $this->assertEquals(202, $deployment['headers']['status-code']); - $this->assertDeployment($function['body']['$id'], $deploymentId, allowFailure: true); - - $deployment = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), []); - - $this->assertEquals(200, $deployment['headers']['status-code']); + $this->assertDeployment($function['body']['$id'], $deploymentId); $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([ 'content-type' => 'application/json', @@ -1864,17 +1842,7 @@ class FunctionsCustomServerTest extends Scope $deploymentId = $deployment['body']['$id'] ?? ''; $this->assertEquals(202, $deployment['headers']['status-code']); - $this->assertDeployment($function['body']['$id'], $deploymentId, allowFailure: true); - - $deployment = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), []); - - $this->assertEquals(200, $deployment['headers']['status-code']); - - // Wait a little for activation to finish - sleep(5); + $this->assertDeployment($function['body']['$id'], $deploymentId); $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([ 'x-appwrite-project' => $this->getProject()['$id'], @@ -1950,17 +1918,7 @@ class FunctionsCustomServerTest extends Scope $deploymentId = $deployment['body']['$id'] ?? ''; $this->assertEquals(202, $deployment['headers']['status-code']); - $this->assertDeployment($function['body']['$id'], $deploymentId, allowFailure: true); - - $deployment = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), []); - - $this->assertEquals(200, $deployment['headers']['status-code']); - - // Wait a little for activation to finish - sleep(5); + $this->assertDeployment($function['body']['$id'], $deploymentId); $bytes = pack('C*', ...[0, 20, 255]); @@ -2050,14 +2008,7 @@ class FunctionsCustomServerTest extends Scope $deploymentId = $deployment['body']['$id'] ?? ''; $this->assertEquals(202, $deployment['headers']['status-code']); - $this->assertDeployment($function['body']['$id'], $deploymentId, allowFailure: true); - - $deployment = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), []); - - $this->assertEquals(200, $deployment['headers']['status-code']); + $this->assertDeployment($function['body']['$id'], $deploymentId); $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([ 'content-type' => 'application/json', @@ -2143,17 +2094,7 @@ class FunctionsCustomServerTest extends Scope $deploymentId = $deployment['body']['$id'] ?? ''; $this->assertEquals(202, $deployment['headers']['status-code']); - $this->assertDeployment($function['body']['$id'], $deploymentId, allowFailure: true); - - $deployment = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), []); - - $this->assertEquals(200, $deployment['headers']['status-code']); - - // Wait a little for activation to finish - sleep(5); + $this->assertDeployment($function['body']['$id'], $deploymentId); // Create user to trigger event $user = $this->client->call(Client::METHOD_POST, '/users', array_merge([ @@ -2241,7 +2182,7 @@ class FunctionsCustomServerTest extends Scope $deploymentId = $deployment['body']['$id'] ?? ''; $this->assertEquals(202, $deployment['headers']['status-code']); - $this->assertDeployment($function['body']['$id'], $deploymentId, allowFailure: true); + $this->assertDeployment($function['body']['$id'], $deploymentId); $deployment = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/deployments/' . $deploymentId, array_merge([ 'content-type' => 'application/json', @@ -2344,17 +2285,7 @@ class FunctionsCustomServerTest extends Scope $deploymentId = $deployment['body']['$id'] ?? ''; $this->assertEquals(202, $deployment['headers']['status-code']); - $this->assertDeployment($function['body']['$id'], $deploymentId, allowFailure: true); - - $deployment = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), []); - - $this->assertEquals(200, $deployment['headers']['status-code']); - - // Wait a little for activation to finish - sleep(5); + $this->assertDeployment($function['body']['$id'], $deploymentId); $cookie = 'cookieName=cookieValue; cookie2=value2; cookie3=value=3; cookie4=val:ue4; cookie5=value5'; @@ -2435,17 +2366,7 @@ class FunctionsCustomServerTest extends Scope $deploymentId = $deployment['body']['$id'] ?? ''; $this->assertEquals(202, $deployment['headers']['status-code']); - $this->assertDeployment($function['body']['$id'], $deploymentId, allowFailure: true); - - $deployment = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), []); - - $this->assertEquals(200, $deployment['headers']['status-code']); - - // Wait a little for activation to finish - sleep(5); + $this->assertDeployment($function['body']['$id'], $deploymentId); $cookie = 'cookieName=cookieValue; cookie2=value2; cookie3=value=3; cookie4=val:ue4; cookie5=value5'; @@ -2551,17 +2472,7 @@ class FunctionsCustomServerTest extends Scope $deploymentId = $deployment['body']['$id'] ?? ''; $this->assertEquals(202, $deployment['headers']['status-code']); - $this->assertDeployment($function['body']['$id'], $deploymentId, allowFailure: true); - - $deployment = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), []); - - $this->assertEquals(200, $deployment['headers']['status-code']); - - // Wait a little for activation to finish - sleep(5); + $this->assertDeployment($function['body']['$id'], $deploymentId); $proxyClient = new Client(); $proxyClient->setEndpoint('http://' . $domain); @@ -2637,17 +2548,7 @@ class FunctionsCustomServerTest extends Scope $deploymentId = $deployment['body']['$id'] ?? ''; $this->assertEquals(202, $deployment['headers']['status-code']); - $this->assertDeployment($function['body']['$id'], $deploymentId, allowFailure: true); - - $deployment = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), []); - - $this->assertEquals(200, $deployment['headers']['status-code']); - - // Wait a little for activation to finish - sleep(5); + $this->assertDeployment($function['body']['$id'], $deploymentId); $proxyClient = new Client(); $proxyClient->setEndpoint('http://' . $domain); @@ -2733,7 +2634,7 @@ class FunctionsCustomServerTest extends Scope $deploymentId = $deployment['body']['$id'] ?? ''; - $this->assertDeployment($functionId, $deploymentId, allowFailure: true); + $this->assertDeployment($functionId, $deploymentId); // Sync Executions test $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([ From 979fcacd6cab9bb48aae251f665ad5b576c803ba Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Thu, 19 Sep 2024 12:35:52 +0100 Subject: [PATCH 03/33] fix: abstract requests --- .../e2e/Services/Functions/FunctionsBase.php | 391 +++++---- .../Functions/FunctionsConsoleClientTest.php | 173 ++-- .../Functions/FunctionsCustomClientTest.php | 739 +++--------------- .../Functions/FunctionsCustomServerTest.php | 358 +++------ 4 files changed, 469 insertions(+), 1192 deletions(-) diff --git a/tests/e2e/Services/Functions/FunctionsBase.php b/tests/e2e/Services/Functions/FunctionsBase.php index 7f33e0b2ee..92210a3027 100644 --- a/tests/e2e/Services/Functions/FunctionsBase.php +++ b/tests/e2e/Services/Functions/FunctionsBase.php @@ -3,6 +3,7 @@ namespace Tests\E2E\Services\Functions; use Appwrite\Tests\Async; +use CURLFile; use Tests\E2E\Client; use Utopia\CLI\Console; @@ -13,220 +14,212 @@ trait FunctionsBase protected string $stdout = ''; protected string $stderr = ''; - protected function packageCode($folder) + protected function setupFunction(mixed $params): string { + $function = $this->client->call(Client::METHOD_POST, '/functions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]), $params); + $functionId = $function['body']['$id']; + + $this->assertEquals($function['headers']['status-code'], 201); + return $functionId; + } + + protected function setupDeployment(string $functionId, mixed $params): string + { + $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]), $params); + $deploymentId = $deployment['body']['$id']; + + $this->assertEquals($deployment['headers']['status-code'], 202); + + $this->assertEventually(function () use ($functionId, $deploymentId) { + $deployment = $this->getDeployment($functionId, $deploymentId); + $this->assertEquals('completed', $deployment['body']['status']); + }, 200000, 500); + + return $deploymentId; + } + + protected function cleanupFunction(string $functionId): void + { + $function = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ])); + + $this->assertEquals($function['headers']['status-code'], 204); + } + + protected function createFunction(mixed $params) + { + $function = $this->client->call(Client::METHOD_POST, '/functions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), $params); + $functionId = $function['body']['$id']; + + if (empty($variables)) { + return $function; + } + + $function = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + return $function; + } + + protected function createVariable($functionId, $key, $value) + { + $variable = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/variables', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'key' => $key, + 'value' => $value + ]); + + return $variable; + } + + protected function getFunction($functionId) + { + $function = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + return $function; + } + + protected function getDeployment($functionId, $deploymentId) + { + $deployment = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/deployments/' . $deploymentId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + return $deployment; + } + + protected function getExecution($functionId, $executionId) + { + $execution = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions/' . $executionId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + return $execution; + } + + protected function listDeployments($functionId, $params = []) + { + $deployments = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/deployments', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders(), $params)); + + return $deployments; + } + + protected function listExecutions($functionId, $params = []) + { + $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders(), $params)); + + return $executions; + } + + protected function packageFunction(string $folder = 'php') + { + $code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz"; + Console::execute('cd ' . realpath(__DIR__ . "/../../../resources/functions") . "/$folder && tar --exclude code.tar.gz -czf code.tar.gz .", '', $this->stdout, $this->stderr); + + if (!file_exists($code)) { + throw new \Exception('Failed to create code package. ' . $code . ' does not exist.'); + } + if (filesize($code) > 1024 * 1024 * 5) { + throw new \Exception('Code package is too large. Use the chunked upload method instead.'); + } + + return new CURLFile($code, 'application/x-gzip', \basename($code)); } - protected function assertDeployment($functionId, $deploymentId, $allowFailure = false) - { - $this->assertEventually(function () use ($functionId, $deploymentId, $allowFailure) { - $deployment = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/deployments/' . $deploymentId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], + protected function createDeployment( + string $functionId, + mixed $params, + bool $cli = false, + bool $admin = false + ) { + $authHeaders = $this->getHeaders(); + + if ($admin) { + $authHeaders = [ 'x-appwrite-key' => $this->getProject()['apiKey'], - ]); + ]; + } + if ($cli) { + $authHeaders[] = 'x-sdk-language: cli'; + } - $status = $deployment['body']['status']; + $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', [ + 'content-type' => 'multipart/form-data', + 'x-appwrite-project' => $this->getProject()['$id'], - if ($allowFailure && $status === 'failed') { - $this->fail('Deployment failed: ' . json_encode($deployment['body']['buildStderr'])); - } + ], $params); - $this->assertEquals('ready', $status); - }, 180000, 1000); + return $deployment; } - // /** - // * @depends testCreateTeam - // */ - // public function testGetTeam($data):array - // { - // $id = $data['teamUid'] ?? ''; + protected function getFunctionUsage($functionId, $params) + { + $usage = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/usage', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders(), $params)); - // /** - // * Test for SUCCESS - // */ - // $response = $this->client->call(Client::METHOD_GET, '/teams/'.$id, array_merge([ - // 'content-type' => 'application/json', - // 'x-appwrite-project' => $this->getProject()['$id'], - // ], $this->getHeaders())); + return $usage; + } - // $this->assertEquals(200, $response['headers']['status-code']); - // $this->assertNotEmpty($response['body']['$id']); - // $this->assertEquals('Arsenal', $response['body']['name']); - // $this->assertGreaterThan(-1, $response['body']['total']); - // $this->assertIsInt($response['body']['total']); - // $this->assertIsInt($response['body']['dateCreated']); + protected function getTemplate(string $templateId) + { + $template = $this->client->call(Client::METHOD_GET, '/functions/templates/' . $templateId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); - // /** - // * Test for FAILURE - // */ + return $template; + } - // return []; - // } + protected function createExecution(string $functionId, mixed $params) + { + $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), $params); - // /** - // * @depends testCreateTeam - // */ - // public function testListTeams($data):array - // { - // /** - // * Test for SUCCESS - // */ - // $response = $this->client->call(Client::METHOD_GET, '/teams', array_merge([ - // 'content-type' => 'application/json', - // 'x-appwrite-project' => $this->getProject()['$id'], - // ], $this->getHeaders())); + return $execution; + } - // $this->assertEquals(200, $response['headers']['status-code']); - // $this->assertGreaterThan(0, $response['body']['total']); - // $this->assertIsInt($response['body']['total']); - // $this->assertCount(3, $response['body']['teams']); + protected function deleteFunction($functionId) + { + $function = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]); - // $response = $this->client->call(Client::METHOD_GET, '/teams', array_merge([ - // 'content-type' => 'application/json', - // 'x-appwrite-project' => $this->getProject()['$id'], - // ], $this->getHeaders()), [ - // 'limit' => 2, - // ]); - - // $this->assertEquals(200, $response['headers']['status-code']); - // $this->assertGreaterThan(0, $response['body']['total']); - // $this->assertIsInt($response['body']['total']); - // $this->assertCount(2, $response['body']['teams']); - - // $response = $this->client->call(Client::METHOD_GET, '/teams', array_merge([ - // 'content-type' => 'application/json', - // 'x-appwrite-project' => $this->getProject()['$id'], - // ], $this->getHeaders()), [ - // 'offset' => 1, - // ]); - - // $this->assertEquals(200, $response['headers']['status-code']); - // $this->assertGreaterThan(0, $response['body']['total']); - // $this->assertIsInt($response['body']['total']); - // $this->assertCount(2, $response['body']['teams']); - - // $response = $this->client->call(Client::METHOD_GET, '/teams', array_merge([ - // 'content-type' => 'application/json', - // 'x-appwrite-project' => $this->getProject()['$id'], - // ], $this->getHeaders()), [ - // 'search' => 'Manchester', - // ]); - - // $this->assertEquals(200, $response['headers']['status-code']); - // $this->assertGreaterThan(0, $response['body']['total']); - // $this->assertIsInt($response['body']['total']); - // $this->assertCount(1, $response['body']['teams']); - // $this->assertEquals('Manchester United', $response['body']['teams'][0]['name']); - - // $response = $this->client->call(Client::METHOD_GET, '/teams', array_merge([ - // 'content-type' => 'application/json', - // 'x-appwrite-project' => $this->getProject()['$id'], - // ], $this->getHeaders()), [ - // 'search' => 'United', - // ]); - - // $this->assertEquals(200, $response['headers']['status-code']); - // $this->assertGreaterThan(0, $response['body']['total']); - // $this->assertIsInt($response['body']['total']); - // $this->assertCount(1, $response['body']['teams']); - // $this->assertEquals('Manchester United', $response['body']['teams'][0]['name']); - - // /** - // * Test for FAILURE - // */ - - // return []; - // } - - // public function testUpdateTeam():array - // { - // /** - // * Test for SUCCESS - // */ - // $response = $this->client->call(Client::METHOD_POST, '/teams', array_merge([ - // 'content-type' => 'application/json', - // 'x-appwrite-project' => $this->getProject()['$id'], - // ], $this->getHeaders()), [ - // 'name' => 'Demo' - // ]); - - // $this->assertEquals(201, $response['headers']['status-code']); - // $this->assertNotEmpty($response['body']['$id']); - // $this->assertEquals('Demo', $response['body']['name']); - // $this->assertGreaterThan(-1, $response['body']['total']); - // $this->assertIsInt($response['body']['total']); - // $this->assertIsInt($response['body']['dateCreated']); - - // $response = $this->client->call(Client::METHOD_PUT, '/teams/'.$response['body']['$id'], array_merge([ - // 'content-type' => 'application/json', - // 'x-appwrite-project' => $this->getProject()['$id'], - // ], $this->getHeaders()), [ - // 'name' => 'Demo New' - // ]); - - // $this->assertEquals(200, $response['headers']['status-code']); - // $this->assertNotEmpty($response['body']['$id']); - // $this->assertEquals('Demo New', $response['body']['name']); - // $this->assertGreaterThan(-1, $response['body']['total']); - // $this->assertIsInt($response['body']['total']); - // $this->assertIsInt($response['body']['dateCreated']); - - // /** - // * Test for FAILURE - // */ - // $response = $this->client->call(Client::METHOD_PUT, '/teams/'.$response['body']['$id'], array_merge([ - // 'content-type' => 'application/json', - // 'x-appwrite-project' => $this->getProject()['$id'], - // ], $this->getHeaders()), [ - // ]); - - // $this->assertEquals(400, $response['headers']['status-code']); - - // return []; - // } - - // public function testDeleteTeam():array - // { - // /** - // * Test for SUCCESS - // */ - // $response = $this->client->call(Client::METHOD_POST, '/teams', array_merge([ - // 'content-type' => 'application/json', - // 'x-appwrite-project' => $this->getProject()['$id'], - // ], $this->getHeaders()), [ - // 'name' => 'Demo' - // ]); - - // $teamUid = $response['body']['$id']; - - // $this->assertEquals(201, $response['headers']['status-code']); - // $this->assertNotEmpty($response['body']['$id']); - // $this->assertEquals('Demo', $response['body']['name']); - // $this->assertGreaterThan(-1, $response['body']['total']); - // $this->assertIsInt($response['body']['total']); - // $this->assertIsInt($response['body']['dateCreated']); - - // $response = $this->client->call(Client::METHOD_DELETE, '/teams/'.$teamUid, array_merge([ - // 'content-type' => 'application/json', - // 'x-appwrite-project' => $this->getProject()['$id'], - // ], $this->getHeaders())); - - // $this->assertEquals(204, $response['headers']['status-code']); - // $this->assertEmpty($response['body']); - - // /** - // * Test for FAILURE - // */ - // $response = $this->client->call(Client::METHOD_GET, '/teams/'.$teamUid, array_merge([ - // 'content-type' => 'application/json', - // 'x-appwrite-project' => $this->getProject()['$id'], - // ], $this->getHeaders())); - - // $this->assertEquals(404, $response['headers']['status-code']); - - // return []; - // } + return $function; + } } diff --git a/tests/e2e/Services/Functions/FunctionsConsoleClientTest.php b/tests/e2e/Services/Functions/FunctionsConsoleClientTest.php index 8cb7f6f869..6ee5a1085d 100644 --- a/tests/e2e/Services/Functions/FunctionsConsoleClientTest.php +++ b/tests/e2e/Services/Functions/FunctionsConsoleClientTest.php @@ -13,13 +13,11 @@ class FunctionsConsoleClientTest extends Scope { use ProjectCustom; use SideConsole; + use FunctionsBase; public function testCreateFunction(): array { - $function = $this->client->call(Client::METHOD_POST, '/functions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + $function = $this->createFunction([ 'functionId' => ID::unique(), 'name' => 'Test', 'execute' => [Role::user($this->getUser()['$id'])->toString()], @@ -32,87 +30,69 @@ class FunctionsConsoleClientTest extends Scope 'schedule' => '0 0 1 1 *', 'timeout' => 10, ]); - + $functionId = $function['body']['$id']; $this->assertEquals(201, $function['headers']['status-code']); - $response = $this->client->call(Client::METHOD_POST, '/functions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + $function2 = $this->createFunction([ 'functionId' => ID::unique(), 'name' => 'Test Failure', 'execute' => ['some-random-string'], 'runtime' => 'php-8.0', 'entrypoint' => 'index.php', ]); - - $this->assertEquals(400, $response['headers']['status-code']); + $this->assertEquals(400, $function2['headers']['status-code']); return [ - 'functionId' => $function['body']['$id'] + 'functionId' => $functionId, ]; } /** * @depends testCreateFunction */ - public function testGetCollectionUsage(array $data) + public function testFunctionUsage(array $data) { - /** - * Test for FAILURE - */ - - $response = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/usage', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'] - ], $this->getHeaders()), [ - 'range' => '232h' - ]); - - $this->assertEquals(400, $response['headers']['status-code']); - - $response = $this->client->call(Client::METHOD_GET, '/functions/randomFunctionId/usage', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'] - ], $this->getHeaders()), [ - 'range' => '24h' - ]); - - $this->assertEquals(404, $response['headers']['status-code']); - /** * Test for SUCCESS */ - - $response = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/usage', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'] - ], $this->getHeaders()), [ + $usage = $this->getFunctionUsage($data['functionId'], [ 'range' => '24h' ]); + $this->assertEquals(200, $usage['headers']['status-code']); + $this->assertEquals(19, count($usage['body'])); + $this->assertEquals('24h', $usage['body']['range']); + $this->assertIsNumeric($usage['body']['deploymentsTotal']); + $this->assertIsNumeric($usage['body']['deploymentsStorageTotal']); + $this->assertIsNumeric($usage['body']['buildsTotal']); + $this->assertIsNumeric($usage['body']['buildsStorageTotal']); + $this->assertIsNumeric($usage['body']['buildsTimeTotal']); + $this->assertIsNumeric($usage['body']['buildsMbSecondsTotal']); + $this->assertIsNumeric($usage['body']['executionsTotal']); + $this->assertIsNumeric($usage['body']['executionsTimeTotal']); + $this->assertIsNumeric($usage['body']['executionsMbSecondsTotal']); + $this->assertIsArray($usage['body']['deployments']); + $this->assertIsArray($usage['body']['deploymentsStorage']); + $this->assertIsArray($usage['body']['builds']); + $this->assertIsArray($usage['body']['buildsTime']); + $this->assertIsArray($usage['body']['buildsStorage']); + $this->assertIsArray($usage['body']['buildsTime']); + $this->assertIsArray($usage['body']['buildsMbSeconds']); + $this->assertIsArray($usage['body']['executions']); + $this->assertIsArray($usage['body']['executionsTime']); + $this->assertIsArray($usage['body']['executionsMbSeconds']); - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals(19, count($response['body'])); - $this->assertEquals('24h', $response['body']['range']); - $this->assertIsNumeric($response['body']['deploymentsTotal']); - $this->assertIsNumeric($response['body']['deploymentsStorageTotal']); - $this->assertIsNumeric($response['body']['buildsTotal']); - $this->assertIsNumeric($response['body']['buildsStorageTotal']); - $this->assertIsNumeric($response['body']['buildsTimeTotal']); - $this->assertIsNumeric($response['body']['buildsMbSecondsTotal']); - $this->assertIsNumeric($response['body']['executionsTotal']); - $this->assertIsNumeric($response['body']['executionsTimeTotal']); - $this->assertIsNumeric($response['body']['executionsMbSecondsTotal']); - $this->assertIsArray($response['body']['deployments']); - $this->assertIsArray($response['body']['deploymentsStorage']); - $this->assertIsArray($response['body']['builds']); - $this->assertIsArray($response['body']['buildsTime']); - $this->assertIsArray($response['body']['buildsStorage']); - $this->assertIsArray($response['body']['buildsTime']); - $this->assertIsArray($response['body']['buildsMbSeconds']); - $this->assertIsArray($response['body']['executions']); - $this->assertIsArray($response['body']['executionsTime']); - $this->assertIsArray($response['body']['executionsMbSeconds']); + /** + * Test for FAILURE + */ + $usage = $this->getFunctionUsage($data['functionId'], [ + 'range' => '232h' + ]); + $this->assertEquals(400, $usage['headers']['status-code']); + + $usage = $this->getFunctionUsage('randomFunctionId', [ + 'range' => '24h' + ]); + $this->assertEquals(404, $usage['headers']['status-code']); } /** @@ -123,31 +103,40 @@ class FunctionsConsoleClientTest extends Scope /** * Test for SUCCESS */ - - $response = $this->client->call(Client::METHOD_POST, '/functions/' . $data['functionId'] . '/variables', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'key' => 'APP_TEST', - 'value' => 'TESTINGVALUE' - ]); - - $this->assertEquals(201, $response['headers']['status-code']); - $variableId = $response['body']['$id']; + $variable = $this->createVariable( + $data['functionId'], + 'APP_TEST', + 'TESTINGVALUE' + ); + $variableId = $variable['body']['$id']; + $this->assertEquals(201, $variable['headers']['status-code']); /** * Test for FAILURE */ + // Test for duplicate key + $variable = $this->createVariable( + $data['functionId'], + 'APP_TEST', + 'ANOTHER_TESTINGVALUE' + ); + $this->assertEquals(409, $variable['headers']['status-code']); - $response = $this->client->call(Client::METHOD_POST, '/functions/' . $data['functionId'] . '/variables', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'key' => 'APP_TEST', - 'value' => 'ANOTHER_TESTINGVALUE' - ]); + // Test for invalid key + $variable = $this->createVariable( + $data['functionId'], + str_repeat("A", 256), + 'TESTINGVALUE' + ); + $this->assertEquals(400, $variable['headers']['status-code']); - $this->assertEquals(409, $response['headers']['status-code']); + // Test for invalid value + $variable = $this->createVariable( + $data['functionId'], + 'LONGKEY', + str_repeat("#", 8193) + ); + $this->assertEquals(400, $variable['headers']['status-code']); return array_merge( $data, @@ -155,28 +144,6 @@ class FunctionsConsoleClientTest extends Scope 'variableId' => $variableId ] ); - - $longKey = str_repeat("A", 256); - $response = $this->client->call(Client::METHOD_POST, '/functions/' . $data['functionId'] . '/variables', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'key' => $longKey, - 'value' => 'TESTINGVALUE' - ]); - - $this->assertEquals(400, $response['headers']['status-code']); - - $longValue = str_repeat("#", 8193); - $response = $this->client->call(Client::METHOD_POST, '/functions/' . $data['functionId'] . '/variables', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'key' => 'LONGKEY', - 'value' => $longValue - ]); - - $this->assertEquals(400, $response['headers']['status-code']); } /** diff --git a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php index f5f15a0fc6..872249085e 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php @@ -2,16 +2,12 @@ namespace Tests\E2E\Services\Functions; -use CURLFile; use Tests\E2E\Client; use Tests\E2E\Scopes\ProjectCustom; use Tests\E2E\Scopes\Scope; use Tests\E2E\Scopes\SideClient; -use Utopia\Config\Config; -use Utopia\Database\Document; use Utopia\Database\Helpers\ID; use Utopia\Database\Helpers\Role; -use Utopia\Database\Query; class FunctionsCustomClientTest extends Scope { @@ -19,15 +15,12 @@ class FunctionsCustomClientTest extends Scope use ProjectCustom; use SideClient; - public function testCreate(): array + public function testCreate() { /** - * Test for SUCCESS + * Test for FAILURE */ - $response1 = $this->client->call(Client::METHOD_POST, '/functions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + $function = $this->createFunction([ 'functionId' => ID::unique(), 'name' => 'Test', 'events' => [ @@ -37,22 +30,15 @@ class FunctionsCustomClientTest extends Scope 'schedule' => '0 0 1 1 *', 'timeout' => 10, ]); - - $this->assertEquals(401, $response1['headers']['status-code']); - - return []; + $this->assertEquals(401, $function['headers']['status-code']); } - public function testCreateExecution(): array + public function testCreateExecution() { /** * Test for SUCCESS */ - $function = $this->client->call(Client::METHOD_POST, '/functions', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], [ + $functionId = $this->setupFunction([ 'functionId' => ID::unique(), 'name' => 'Test', 'execute' => [Role::user($this->getUser()['$id'])->toString()], @@ -62,121 +48,41 @@ class FunctionsCustomClientTest extends Scope 'users.*.create', 'users.*.delete', ], - 'schedule' => '* * * * *', // execute every minute + 'schedule' => '* * * * *', // Execute every minute 'timeout' => 10, ]); - - $this->assertEquals(201, $function['headers']['status-code']); - - /** Create Variables */ - $variable = $this->client->call(Client::METHOD_POST, '/functions/' . $function['body']['$id'] . '/variables', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], [ - 'key' => 'funcKey1', - 'value' => 'funcValue1', - ]); - - $variable2 = $this->client->call(Client::METHOD_POST, '/functions/' . $function['body']['$id'] . '/variables', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], [ - 'key' => 'funcKey2', - 'value' => 'funcValue2', - ]); - - $variable3 = $this->client->call(Client::METHOD_POST, '/functions/' . $function['body']['$id'] . '/variables', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], [ - 'key' => 'funcKey3', - 'value' => 'funcValue3', - ]); - - $this->assertEquals(201, $variable['headers']['status-code']); - $this->assertEquals(201, $variable2['headers']['status-code']); - $this->assertEquals(201, $variable3['headers']['status-code']); - - $folder = 'php'; - $code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz"; - $this->packageCode($folder); - - $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $function['body']['$id'] . '/deployments', [ - 'content-type' => 'multipart/form-data', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], [ + $this->setupDeployment($functionId, [ 'entrypoint' => 'index.php', - 'code' => new CURLFile($code, 'application/x-gzip', \basename($code)), + 'code' => $this->packageFunction('php'), 'activate' => true ]); - $deploymentId = $deployment['body']['$id'] ?? ''; - - $this->assertEquals(202, $deployment['headers']['status-code']); - - $this->assertDeployment($function['body']['$id'], $deploymentId); - - $function = $this->client->call(Client::METHOD_PATCH, '/functions/' . $function['body']['$id'] . '/deployments/' . $deploymentId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], []); - - $this->assertEquals(200, $function['headers']['status-code']); - - $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $function['body']['$id'] . '/executions', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], [ - 'async' => true, + $execution = $this->createExecution($functionId, [ + 'async' => false, ]); - $this->assertEquals(401, $execution['headers']['status-code']); - $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $function['body']['$id'] . '/executions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + $execution = $this->createExecution($functionId, [ 'async' => true, ]); - $this->assertEquals(202, $execution['headers']['status-code']); - $this->assertEventually(function () use ($function) { - $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $function['body']['$id'] . '/executions', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ]); - + $this->assertEventually(function () use ($functionId) { + $executions = $this->listExecutions($functionId); $this->assertEquals(200, $executions['headers']['status-code']); $this->assertCount(2, $executions['body']['executions']); - // Check if the scheduled execution has completed - $scheduledExecution = $executions['body']['executions'][1]; - $this->assertEquals('schedule', $scheduledExecution['trigger']); - $this->assertEquals('completed', $scheduledExecution['status']); - $this->assertEquals(200, $scheduledExecution['responseStatusCode']); - $this->assertEquals('', $scheduledExecution['responseBody']); - $this->assertNotEmpty($scheduledExecution['logs']); - $this->assertNotEmpty($scheduledExecution['errors']); - $this->assertGreaterThan(0, $scheduledExecution['duration']); - }, 120000, 2000); + $asyncExecution = $executions['body']['executions'][1]; + $this->assertEquals('schedule', $asyncExecution['trigger']); + $this->assertEquals('completed', $asyncExecution['status']); + $this->assertEquals(200, $asyncExecution['responseStatusCode']); + $this->assertEquals('', $asyncExecution['responseBody']); + $this->assertNotEmpty($asyncExecution['logs']); + $this->assertNotEmpty($asyncExecution['errors']); + $this->assertGreaterThan(0, $asyncExecution['duration']); + }, 100000, 250); - // Cleanup : Delete function - $response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $function['body']['$id'], [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], []); - - $this->assertEquals(204, $response['headers']['status-code']); - - return []; + $this->cleanupFunction($functionId); } public function testCreateScheduledExecution(): void @@ -184,11 +90,7 @@ class FunctionsCustomClientTest extends Scope /** * Test for SUCCESS */ - $function = $this->client->call(Client::METHOD_POST, '/functions', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], [ + $functionId = $this->setupFunction([ 'functionId' => ID::unique(), 'name' => 'Test', 'execute' => [Role::user($this->getUser()['$id'])->toString()], @@ -196,38 +98,20 @@ class FunctionsCustomClientTest extends Scope 'entrypoint' => 'index.php', 'timeout' => 10, ]); - - $this->assertEquals(201, $function['headers']['status-code']); - - $folder = 'php'; - $code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz"; - $this->packageCode($folder); - - $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $function['body']['$id'] . '/deployments', [ - 'content-type' => 'multipart/form-data', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], [ + $this->setupDeployment($functionId, [ 'entrypoint' => 'index.php', - 'code' => new CURLFile($code, 'application/x-gzip', \basename($code)), + 'code' => $this->packageFunction('php'), 'activate' => true ]); - $deploymentId = $deployment['body']['$id'] ?? ''; - $this->assertEquals(202, $deployment['headers']['status-code']); - - $this->assertDeployment($function['body']['$id'], $deploymentId, true); // Schedule execution for the future \date_default_timezone_set('UTC'); - $futureTime = (new \DateTime())->add(new \DateInterval('PT2M')); + $futureTime = (new \DateTime())->add(new \DateInterval('PT30S')); // 30 seconds from now $futureTime->setTime($futureTime->format('H'), $futureTime->format('i'), 0, 0); - $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $function['body']['$id'] . '/executions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + $execution = $this->createExecution($functionId, [ 'async' => true, - 'scheduledAt' => $futureTime->format(\DateTime::ATOM), + 'scheduledAt' => $futureTime->format(\DateTime::ATOM), 'path' => '/custom-path', 'method' => 'PATCH', 'body' => 'custom-body', @@ -235,101 +119,60 @@ class FunctionsCustomClientTest extends Scope 'x-custom-header' => 'custom-value' ] ]); - + $executionId = $execution['body']['$id']; $this->assertEquals(202, $execution['headers']['status-code']); $this->assertEquals('scheduled', $execution['body']['status']); $this->assertEquals('PATCH', $execution['body']['requestMethod']); $this->assertEquals('/custom-path', $execution['body']['requestPath']); $this->assertCount(0, $execution['body']['requestHeaders']); - $executionId = $execution['body']['$id']; - - $this->assertEventually(function () use ($function, $executionId) { - $execution = $this->client->call(Client::METHOD_GET, '/functions/' . $function['body']['$id'] . '/executions/' . $executionId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ]); - + $this->assertEventually(function () use ($functionId, $executionId) { + $execution = $this->getExecution($functionId, $executionId); $this->assertEquals('completed', $execution['body']['status']); - }, 150000, 2000); // Timeout after 150 seconds, wait 2 seconds between retries - - // After ensuring the execution is completed, fetch it again for assertions - $execution = $this->client->call(Client::METHOD_GET, '/functions/' . $function['body']['$id'] . '/executions/' . $executionId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ]); - - $this->assertEquals(200, $execution['headers']['status-code']); - $this->assertEquals(200, $execution['body']['responseStatusCode']); - $this->assertEquals('completed', $execution['body']['status']); - $this->assertEquals('/custom-path', $execution['body']['requestPath']); - $this->assertEquals('PATCH', $execution['body']['requestMethod']); - $this->assertStringContainsString('body-is-custom-body', $execution['body']['logs']); - $this->assertStringContainsString('custom-header-is-custom-value', $execution['body']['logs']); - $this->assertStringContainsString('method-is-patch', $execution['body']['logs']); - $this->assertStringContainsString('path-is-/custom-path', $execution['body']['logs']); - $this->assertStringContainsString('user-is-' . $this->getUser()['$id'], $execution['body']['logs']); - $this->assertStringContainsString('jwt-is-valid', $execution['body']['logs']); - $this->assertGreaterThan(0, $execution['body']['duration']); + $this->assertEquals(200, $execution['headers']['status-code']); + $this->assertEquals(200, $execution['body']['responseStatusCode']); + $this->assertEquals('completed', $execution['body']['status']); + $this->assertEquals('/custom-path', $execution['body']['requestPath']); + $this->assertEquals('PATCH', $execution['body']['requestMethod']); + $this->assertStringContainsString('body-is-custom-body', $execution['body']['logs']); + $this->assertStringContainsString('custom-header-is-custom-value', $execution['body']['logs']); + $this->assertStringContainsString('method-is-patch', $execution['body']['logs']); + $this->assertStringContainsString('path-is-/custom-path', $execution['body']['logs']); + $this->assertStringContainsString('user-is-' . $this->getUser()['$id'], $execution['body']['logs']); + $this->assertStringContainsString('jwt-is-valid', $execution['body']['logs']); + $this->assertGreaterThan(0, $execution['body']['duration']); + }, 40000, 2500); /* Test for FAILURE */ - // Schedule synchronous execution - $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $function['body']['$id'] . '/executions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + $execution = $this->createExecution($functionId, [ 'async' => false, - 'scheduledAt' => $futureTime->format(\DateTime::ATOM), + 'scheduledAt' => $futureTime->format(\DateTime::ATOM), ]); - $this->assertEquals(400, $execution['headers']['status-code']); // Execution with seconds precision - $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $function['body']['$id'] . '/executions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + $execution = $this->createExecution($functionId, [ 'async' => true, 'scheduledAt' => (new \DateTime("2100-12-08 16:12:02"))->format(\DateTime::ATOM) ]); - $this->assertEquals(400, $execution['headers']['status-code']); // Execution with milliseconds precision - $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $function['body']['$id'] . '/executions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + $execution = $this->createExecution($functionId, [ 'async' => true, 'scheduledAt' => (new \DateTime("2100-12-08 16:12:02.255"))->format(\DateTime::ATOM) ]); - $this->assertEquals(400, $execution['headers']['status-code']); // Execution too soon - $futureTime = (new \DateTime())->add(new \DateInterval('PT1M')); - $futureTime->setTime($futureTime->format('H'), $futureTime->format('i'), 0, 0); - $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $function['body']['$id'] . '/executions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + $execution = $this->createExecution($functionId, [ 'async' => true, - 'scheduledAt' => $futureTime->format(\DateTime::ATOM), + 'scheduledAt' => (new \DateTime())->add(new \DateInterval('PT1S'))->format(\DateTime::ATOM) ]); - $this->assertEquals(400, $execution['headers']['status-code']); - // Cleanup : Delete function - $response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $function['body']['$id'], [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], []); - - $this->assertEquals(204, $response['headers']['status-code']); + $this->cleanupFunction($functionId, $executionId); } public function testCreateCustomExecution(): array @@ -337,14 +180,7 @@ class FunctionsCustomClientTest extends Scope /** * Test for SUCCESS */ - $projectId = $this->getProject()['$id']; - $apikey = $this->getProject()['apiKey']; - - $function = $this->client->call(Client::METHOD_POST, '/functions', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $projectId, - 'x-appwrite-key' => $apikey, - ], [ + $functionId = $this->setupFunction([ 'functionId' => ID::unique(), 'name' => 'Test', 'execute' => [Role::any()->toString()], @@ -352,77 +188,16 @@ class FunctionsCustomClientTest extends Scope 'entrypoint' => 'index.php', 'timeout' => 10, ]); - - $functionId = $function['body']['$id'] ?? ''; - - $this->assertEquals(201, $function['headers']['status-code']); - - /** Create Variables */ - $variable = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/variables', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $projectId, - 'x-appwrite-key' => $apikey, - ], [ - 'key' => 'funcKey1', - 'value' => 'funcValue1', - ]); - - $variable2 = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/variables', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $projectId, - 'x-appwrite-key' => $apikey, - ], [ - 'key' => 'funcKey2', - 'value' => 'funcValue2', - ]); - - $variable3 = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/variables', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $projectId, - 'x-appwrite-key' => $apikey, - ], [ - 'key' => 'funcKey3', - 'value' => 'funcValue3', - ]); - - $this->assertEquals(201, $variable['headers']['status-code']); - $this->assertEquals(201, $variable2['headers']['status-code']); - $this->assertEquals(201, $variable3['headers']['status-code']); - - $folder = 'php-fn'; - $code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz"; - $this->packageCode($folder); - - $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', [ - 'content-type' => 'multipart/form-data', - 'x-appwrite-project' => $projectId, - 'x-appwrite-key' => $apikey, - ], [ + $deploymentId = $this->setupDeployment($functionId, [ 'entrypoint' => 'index.php', - 'code' => new CURLFile($code, 'application/x-gzip', \basename($code)), //different tarball names intentional + 'code' => $this->packageFunction('php-fn'), 'activate' => true ]); - $deploymentId = $deployment['body']['$id'] ?? ''; - - $this->assertDeployment($function['body']['$id'], $deploymentId); - - $function = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $projectId, - 'x-appwrite-key' => $apikey, - ], []); - - $this->assertEquals(200, $function['headers']['status-code']); - - $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $projectId, - ], $this->getHeaders()), [ + $execution = $this->createExecution($functionId, [ 'body' => 'foobar', 'async' => false ]); - $output = json_decode($execution['body']['responseBody'], true); $this->assertEquals(201, $execution['headers']['status-code']); $this->assertEquals(200, $execution['body']['responseStatusCode']); @@ -440,30 +215,20 @@ class FunctionsCustomClientTest extends Scope $this->assertEquals('foobar', $output['APPWRITE_FUNCTION_DATA']); $this->assertEquals($this->getUser()['$id'], $output['APPWRITE_FUNCTION_USER_ID']); $this->assertNotEmpty($output['APPWRITE_FUNCTION_JWT']); - $this->assertEquals($projectId, $output['APPWRITE_FUNCTION_PROJECT_ID']); + $this->assertEquals($this->getProject()['$id'], $output['APPWRITE_FUNCTION_PROJECT_ID']); - $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $projectId, - ], $this->getHeaders()), [ + $execution = $this->createExecution($functionId, [ 'body' => 'foobar', 'async' => true ]); - + $executionId = $execution['body']['$id']; $this->assertEquals(202, $execution['headers']['status-code']); - $executionId = $execution['body']['$id'] ?? ''; - - $this->assertEventually(function () use ($functionId, $executionId, $projectId, $apikey) { - $execution = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions/' . $executionId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $projectId, - 'x-appwrite-key' => $apikey, - ]); - + $this->assertEventually(function () use ($functionId, $executionId) { + $execution = $this->getExecution($functionId, $executionId); $this->assertEquals('completed', $execution['body']['status']); $this->assertEquals(200, $execution['body']['responseStatusCode']); - }, 30000, 1000); + }, 10000, 250); return [ 'functionId' => $functionId @@ -475,14 +240,7 @@ class FunctionsCustomClientTest extends Scope /** * Test for SUCCESS */ - $projectId = $this->getProject()['$id']; - $apikey = $this->getProject()['apiKey']; - - $function = $this->client->call(Client::METHOD_POST, '/functions', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $projectId, - 'x-appwrite-key' => $apikey, - ], [ + $functionId = $this->setupFunction([ 'functionId' => ID::unique(), 'name' => 'Test', 'execute' => [Role::any()->toString()], @@ -495,56 +253,25 @@ class FunctionsCustomClientTest extends Scope ], 'timeout' => 10, ]); - - $functionId = $function['body']['$id'] ?? ''; - - $this->assertEquals(201, $function['headers']['status-code']); - - $folder = 'php-fn'; - $code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz"; - $this->packageCode($folder); - - $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', [ - 'content-type' => 'multipart/form-data', - 'x-appwrite-project' => $projectId, - 'x-appwrite-key' => $apikey, - ], [ + $this->setupDeployment($functionId, [ 'entrypoint' => 'index.php', - 'code' => new CURLFile($code, 'application/x-gzip', \basename($code)), //different tarball names intentional + 'code' => $this->packageFunction('php-fn'), 'activate' => true ]); - $deploymentId = $deployment['body']['$id'] ?? ''; - - $this->assertDeployment($function['body']['$id'], $deploymentId); - - // Why do we have to do this? - $function = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $projectId, - 'x-appwrite-key' => $apikey, - ], []); - - $this->assertEquals(200, $function['headers']['status-code']); - $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', [ 'content-type' => 'application/json', - 'x-appwrite-project' => $projectId, + 'x-appwrite-project' => $this->getProject()['$id'], ], [ 'data' => 'foobar', 'async' => true, ]); - $this->assertEquals(202, $execution['headers']['status-code']); } - public function testCreateExecutionNoDeployment(): array + public function testCreateExecutionNoDeployment() { - $function = $this->client->call(Client::METHOD_POST, '/functions', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], [ + $functionId = $this->setupFunction([ 'functionId' => ID::unique(), 'name' => 'Test', 'execute' => [], @@ -553,146 +280,18 @@ class FunctionsCustomClientTest extends Scope 'timeout' => 10, ]); - $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $function['body']['$id'] . '/executions', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], [ - 'async' => true, + $execution = $this->createExecution($functionId, [ + 'async' => true ]); - $this->assertEquals(404, $execution['headers']['status-code']); - - return []; } - /** - * @depends testCreateCustomExecution - */ - public function testListExecutions(array $data) - { - $functionId = $data['functionId']; - $projectId = $this->getProject()['$id']; - $apikey = $this->getProject()['apiKey']; - - $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $projectId, - ], $this->getHeaders()), [ - 'data' => 'foobar' - ]); - - $this->assertEquals(201, $execution['headers']['status-code']); - - $base = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $projectId, - 'x-appwrite-key' => $apikey, - ]); - - $this->assertEquals(200, $base['headers']['status-code']); - $this->assertCount(3, $base['body']['executions']); - $this->assertEquals('completed', $base['body']['executions'][0]['status']); - $this->assertEquals('completed', $base['body']['executions'][1]['status']); - - $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $projectId, - 'x-appwrite-key' => $apikey, - ], [ - 'queries' => [ - Query::limit(1)->toString(), - ], - ]); - - $this->assertEquals(200, $executions['headers']['status-code']); - $this->assertCount(1, $executions['body']['executions']); - - $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $projectId, - 'x-appwrite-key' => $apikey, - ], [ - 'queries' => [ - Query::offset(1)->toString(), - ], - ]); - - $this->assertEquals(200, $executions['headers']['status-code']); - $this->assertCount(2, $executions['body']['executions']); - - $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $projectId, - 'x-appwrite-key' => $apikey, - ], [ - 'queries' => [ - Query::equal('status', ['completed'])->toString(), - ], - ]); - - $this->assertEquals(200, $executions['headers']['status-code']); - $this->assertCount(3, $executions['body']['executions']); - - $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $projectId, - 'x-appwrite-key' => $apikey, - ], [ - 'queries' => [ - Query::equal('status', ['failed'])->toString(), - ], - ]); - - $this->assertEquals(200, $executions['headers']['status-code']); - $this->assertCount(0, $executions['body']['executions']); - - $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $projectId, - 'x-appwrite-key' => $apikey, - ], [ - 'queries' => [ - Query::cursorAfter(new Document(['$id' => $base['body']['executions'][0]['$id']]))->toString(), - ], - ]); - - $this->assertCount(2, $executions['body']['executions']); - $this->assertEquals($base['body']['executions'][1]['$id'], $executions['body']['executions'][0]['$id']); - - $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $projectId, - 'x-appwrite-key' => $apikey, - ], [ - 'queries' => [ - Query::cursorBefore(new Document(['$id' => $base['body']['executions'][1]['$id']]))->toString(), - ], - ]); - - // Cleanup : Delete function - $response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], []); - - $this->assertEquals(204, $response['headers']['status-code']); - } - - public function testSynchronousExecution(): array + public function testSynchronousExecution() { /** * Test for SUCCESS */ - - $projectId = $this->getProject()['$id']; - $apikey = $this->getProject()['apiKey']; - - $function = $this->client->call(Client::METHOD_POST, '/functions', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $projectId, - 'x-appwrite-key' => $apikey, - ], [ + $functionId = $this->setupFunction([ 'functionId' => ID::unique(), 'name' => 'Test', 'execute' => [Role::any()->toString()], @@ -700,79 +299,16 @@ class FunctionsCustomClientTest extends Scope 'entrypoint' => 'index.php', 'timeout' => 10, ]); - - $functionId = $function['body']['$id'] ?? ''; - - $this->assertEquals(201, $function['headers']['status-code']); - - /** Create Variables */ - $variable = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/variables', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $projectId, - 'x-appwrite-key' => $apikey, - ], [ - 'key' => 'funcKey1', - 'value' => 'funcValue1', - ]); - - $variable2 = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/variables', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $projectId, - 'x-appwrite-key' => $apikey, - ], [ - 'key' => 'funcKey2', - 'value' => 'funcValue2', - ]); - - $variable3 = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/variables', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $projectId, - 'x-appwrite-key' => $apikey, - ], [ - 'key' => 'funcKey3', - 'value' => 'funcValue3', - ]); - - $this->assertEquals(201, $variable['headers']['status-code']); - $this->assertEquals(201, $variable2['headers']['status-code']); - $this->assertEquals(201, $variable3['headers']['status-code']); - - $folder = 'php-fn'; - $code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz"; - $this->packageCode($folder); - - $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', [ - 'content-type' => 'multipart/form-data', - 'x-appwrite-project' => $projectId, - 'x-appwrite-key' => $apikey, - ], [ + $deploymentId = $this->setupDeployment($functionId, [ 'entrypoint' => 'index.php', - 'code' => new CURLFile($code, 'application/x-gzip', \basename($code)), //different tarball names intentional + 'code' => $this->packageFunction('php-fn'), 'activate' => true ]); - $deploymentId = $deployment['body']['$id'] ?? ''; - - $this->assertEquals(202, $deployment['headers']['status-code']); - - $this->assertDeployment($function['body']['$id'], $deploymentId); - - $function = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $projectId, - 'x-appwrite-key' => $apikey, - ]); - - $this->assertEquals(200, $function['headers']['status-code']); - - $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $projectId, - ], $this->getHeaders()), [ + $execution = $this->createExecution($functionId, [ 'body' => 'foobar', // Testing default value, should be 'async' => false ]); - $output = json_decode($execution['body']['responseBody'], true); $this->assertEquals(201, $execution['headers']['status-code']); $this->assertEquals('completed', $execution['body']['status']); @@ -789,67 +325,29 @@ class FunctionsCustomClientTest extends Scope $this->assertEquals('foobar', $output['APPWRITE_FUNCTION_DATA']); $this->assertEquals($this->getUser()['$id'], $output['APPWRITE_FUNCTION_USER_ID']); $this->assertNotEmpty($output['APPWRITE_FUNCTION_JWT']); - $this->assertEquals($projectId, $output['APPWRITE_FUNCTION_PROJECT_ID']); + $this->assertEquals($this->getProject()['$id'], $output['APPWRITE_FUNCTION_PROJECT_ID']); // Client should never see logs and errors $this->assertEmpty($execution['body']['logs']); $this->assertEmpty($execution['body']['errors']); - // Cleanup : Delete function - $response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], []); - - $this->assertEquals(204, $response['headers']['status-code']); - - return []; + $this->cleanupFunction($functionId); } - public function testNonOverrideOfHeaders(): array + public function testNonOverrideOfHeaders() { - /** - * Test for SUCCESS - */ - $projectId = $this->getProject()['$id']; - $apikey = $this->getProject()['apiKey']; - - $function = $this->client->call(Client::METHOD_POST, '/functions', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $projectId, - 'x-appwrite-key' => $apikey, - ], [ + $functionId = $this->setupFunction([ 'functionId' => ID::unique(), 'name' => 'Test', 'execute' => [Role::any()->toString()], 'runtime' => 'node-18.0', 'entrypoint' => 'index.js' ]); - - $functionId = $function['body']['$id'] ?? ''; - - $this->assertEquals(201, $function['headers']['status-code']); - - $folder = 'node'; - $code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz"; - $this->packageCode($folder); - - $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', [ - 'content-type' => 'multipart/form-data', - 'x-appwrite-project' => $projectId, - 'x-appwrite-key' => $apikey, - ], [ + $this->setupDeployment($functionId, [ 'entrypoint' => 'index.js', - 'code' => new CURLFile($code, 'application/x-gzip', \basename($code)), //different tarball names intentional + 'code' => $this->packageFunction('node'), 'activate' => true ]); - $deploymentId = $deployment['body']['$id'] ?? ''; - - $this->assertEquals(202, $deployment['headers']['status-code']); - - $this->assertDeployment($function['body']['$id'], $deploymentId); - $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -859,23 +357,13 @@ class FunctionsCustomClientTest extends Scope 'x-appwrite-user-id' => "OVERRIDDEN", 'x-appwrite-user-jwt' => "OVERRIDDEN", ]); - $output = json_decode($execution['body']['responseBody'], true); $this->assertNotEquals('OVERRIDDEN', $output['APPWRITE_FUNCTION_JWT']); $this->assertNotEquals('OVERRIDDEN', $output['APPWRITE_FUNCTION_EVENT']); $this->assertNotEquals('OVERRIDDEN', $output['APPWRITE_FUNCTION_TRIGGER']); $this->assertNotEquals('OVERRIDDEN', $output['APPWRITE_FUNCTION_USER_ID']); - // Cleanup : Delete function - $response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], []); - - $this->assertEquals(204, $response['headers']['status-code']); - - return []; + $this->cleanupFunction($functionId); } public function testListTemplates() @@ -883,50 +371,43 @@ class FunctionsCustomClientTest extends Scope /** * Test for SUCCESS */ - $expectedTemplates = array_slice(Config::getParam('function-templates', []), 0, 25); + // List all templates $templates = $this->client->call(Client::METHOD_GET, '/functions/templates', array_merge([ 'content-type' => 'application/json', ], $this->getHeaders())); - $this->assertEquals(200, $templates['headers']['status-code']); $this->assertGreaterThan(0, $templates['body']['total']); $this->assertIsArray($templates['body']['templates']); - - $this->assertArrayHasKey('runtimes', $templates['body']['templates'][0]); - $this->assertArrayHasKey('useCases', $templates['body']['templates'][0]); - for ($i = 0; $i < 25; $i++) { - $this->assertEquals($expectedTemplates[$i]['name'], $templates['body']['templates'][$i]['name']); - $this->assertEquals($expectedTemplates[$i]['id'], $templates['body']['templates'][$i]['id']); - $this->assertEquals($expectedTemplates[$i]['icon'], $templates['body']['templates'][$i]['icon']); - $this->assertEquals($expectedTemplates[$i]['tagline'], $templates['body']['templates'][$i]['tagline']); - $this->assertEquals($expectedTemplates[$i]['useCases'], $templates['body']['templates'][$i]['useCases']); - $this->assertEquals($expectedTemplates[$i]['vcsProvider'], $templates['body']['templates'][$i]['vcsProvider']); - $this->assertEquals($expectedTemplates[$i]['runtimes'], $templates['body']['templates'][$i]['runtimes']); - $this->assertEquals($expectedTemplates[$i]['variables'], $templates['body']['templates'][$i]['variables']); - if (array_key_exists('scopes', $expectedTemplates[$i])) { - $this->assertEquals($expectedTemplates[$i]['scopes'], $templates['body']['templates'][$i]['scopes']); - } + foreach ($templates['body']['templates'] as $template) { + $this->assertArrayHasKey('name', $template); + $this->assertArrayHasKey('id', $template); + $this->assertArrayHasKey('icon', $template); + $this->assertArrayHasKey('tagline', $template); + $this->assertArrayHasKey('useCases', $template); + $this->assertArrayHasKey('vcsProvider', $template); + $this->assertArrayHasKey('runtimes', $template); + $this->assertArrayHasKey('variables', $template); + $this->assertArrayHasKey('scopes', $template); } - $templates_offset = $this->client->call(Client::METHOD_GET, '/functions/templates', array_merge([ + // List templates with pagination + $templatesOffset = $this->client->call(Client::METHOD_GET, '/functions/templates', array_merge([ 'content-type' => 'application/json', ], $this->getHeaders()), [ 'limit' => 1, 'offset' => 2 ]); + $this->assertEquals(200, $templatesOffset['headers']['status-code']); + $this->assertEquals(1, $templatesOffset['body']['total']); + $this->assertEquals($templates['body']['templates'][2]['id'], $templatesOffset['body']['templates'][0]['id']); - $this->assertEquals(200, $templates_offset['headers']['status-code']); - $this->assertEquals(1, $templates_offset['body']['total']); - // assert that offset works as expected - $this->assertEquals($templates['body']['templates'][2]['id'], $templates_offset['body']['templates'][0]['id']); - + // List templates with filters $templates = $this->client->call(Client::METHOD_GET, '/functions/templates', array_merge([ 'content-type' => 'application/json', ], $this->getHeaders()), [ 'useCases' => ['starter', 'ai'], 'runtimes' => ['bun-1.0', 'dart-2.16'] ]); - $this->assertEquals(200, $templates['headers']['status-code']); $this->assertGreaterThanOrEqual(3, $templates['body']['total']); $this->assertIsArray($templates['body']['templates']); @@ -936,6 +417,7 @@ class FunctionsCustomClientTest extends Scope $this->assertArrayHasKey('runtimes', $templates['body']['templates'][0]); $this->assertContains('bun-1.0', array_column($templates['body']['templates'][0]['runtimes'], 'name')); + // List templates with pagination and filters $templates = $this->client->call(Client::METHOD_GET, '/functions/templates', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -958,16 +440,16 @@ class FunctionsCustomClientTest extends Scope /** * Test for FAILURE */ + // List templates with invalid limit $templates = $this->client->call(Client::METHOD_GET, '/functions/templates', array_merge([ 'content-type' => 'application/json', ], $this->getHeaders()), [ 'limit' => 5001, 'offset' => 10, ]); - $this->assertEquals(400, $templates['headers']['status-code']); - $this->assertEquals('Invalid `limit` param: Value must be a valid range between 1 and 5,000', $templates['body']['message']); + // List templates with invalid offset $templates = $this->client->call(Client::METHOD_GET, '/functions/templates', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -975,9 +457,7 @@ class FunctionsCustomClientTest extends Scope 'limit' => 5, 'offset' => 5001, ]); - $this->assertEquals(400, $templates['headers']['status-code']); - $this->assertEquals('Invalid `offset` param: Value must be a valid range between 0 and 5,000', $templates['body']['message']); } public function testGetTemplate() @@ -985,10 +465,7 @@ class FunctionsCustomClientTest extends Scope /** * Test for SUCCESS */ - $template = $this->client->call(Client::METHOD_GET, '/functions/templates/query-neo4j-auradb', array_merge([ - 'content-type' => 'application/json', - ], $this->getHeaders()), []); - + $template = $this->getTemplate('query-neo4j-auradb'); $this->assertEquals(200, $template['headers']['status-code']); $this->assertIsArray($template['body']); $this->assertEquals('query-neo4j-auradb', $template['body']['id']); @@ -997,15 +474,13 @@ class FunctionsCustomClientTest extends Scope $this->assertEquals('Graph database with focus on relations between data.', $template['body']['tagline']); $this->assertEquals(['databases'], $template['body']['useCases']); $this->assertEquals('github', $template['body']['vcsProvider']); + $this->assertIsArray($template['body']['runtimes']); + $this->assertIsArray($template['body']['scopes']); /** * Test for FAILURE */ - $template = $this->client->call(Client::METHOD_GET, '/functions/templates/invalid-template-id', array_merge([ - 'content-type' => 'application/json', - ], $this->getHeaders()), []); - + $template = $this->getTemplate('invalid-template-id'); $this->assertEquals(404, $template['headers']['status-code']); - $this->assertEquals('Function Template with the requested ID could not be found.', $template['body']['message']); } } diff --git a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php index 193e99d89e..8fe4aedca7 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php @@ -388,12 +388,9 @@ class FunctionsCustomServerTest extends Scope public function testCreateDeploymentFromCLI() { - $function = $this->client->call(Client::METHOD_POST, '/functions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + $function = $this->createFunction([ 'functionId' => ID::unique(), - 'name' => 'Test', + 'name' => 'test-cli-deployment', 'execute' => [Role::user($this->getUser()['$id'])->toString()], 'runtime' => 'php-8.0', 'entrypoint' => 'index.php', @@ -401,210 +398,127 @@ class FunctionsCustomServerTest extends Scope 'users.*.create', 'users.*.delete', ], - 'schedule' => '0 0 1 1 *', + 'schedule' => '0 0 1 1 *', // Once a year 'timeout' => 10, ]); + $functionId = $function['body']['$id']; $this->assertEquals(201, $function['headers']['status-code']); - $functionId = $function['body']['$id']; - - $folder = 'php'; - $code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz"; - $this->packageCode($folder); - - $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $function['body']['$id'] . '/deployments', [ - 'content-type' => 'multipart/form-data', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - 'x-sdk-language' => 'cli', - ], [ + $deployment = $this->createDeployment($functionId, [ 'entrypoint' => 'index.php', - 'code' => new CURLFile($code, 'application/x-gzip', \basename($code)), + 'code' => $this->packageFunction('php'), 'activate' => true - ]); - - $deploymentId = $deployment['body']['$id'] ?? ''; + ], cli: true); + $deploymentId = $deployment['body']['$id']; $this->assertEquals(202, $deployment['headers']['status-code']); + $this->assertDeploymentStatus($functionId, $deploymentId, 'ready'); - $this->assertDeployment($function['body']['$id'], $deploymentId); - - $functionDetails = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/deployments/' . $deploymentId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], []); - - $this->assertEquals(200, $functionDetails['headers']['status-code']); - $this->assertEquals('cli', $functionDetails['body']['type']); + $deployment = $this->getDeployment($functionId, $deploymentId); + $this->assertEquals(200, $deployment['headers']['status-code']); + $this->assertEquals('cli', $deployment['body']['type']); } public function testCreateDeploymentFromTemplate() { - $runtimeName = 'php-8.0'; - // Fetch starter template (used to create function later) - $template = $this->client->call(Client::METHOD_GET, '/functions/templates/starter', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); + $starterTemplate = $this->getTemplate('starter'); + $this->assertEquals(200, $starterTemplate['headers']['status-code']); - $this->assertEquals(200, $template['headers']['status-code']); + $phpRuntime = array_values(array_filter($starterTemplate['body']['runtimes'], function ($runtime) { + return $runtime['name'] === 'php-8.0'; + }))[0]; - $entrypoint = null; - $rootDirectory = null; - $commands = null; - foreach ($template['body']['runtimes'] as $runtime) { - if ($runtime["name"] !== $runtimeName) { - continue; - } - - $entrypoint = $runtime["entrypoint"]; - $rootDirectory = $runtime["providerRootDirectory"]; - $commands = $runtime["commands"]; - break; - } - - $this->assertNotNull($entrypoint); - - /** - * If below test ever starts failing, it means temaplate used in - * this test now has some variables. This test currently doesnt test variables. - * Remove bellow assertion and update test to crete variable, - * and ensure variable works as expected in execution. - */ - $this->assertEmpty($template['body']['variables']); - - // Create function using settings from template. - // Deployment is automatically created from template inside endpoint - $function = $this->client->call(Client::METHOD_POST, '/functions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], $this->getHeaders()), [ - 'functionId' => ID::unique(), - 'name' => $template['body']['name'], - 'runtime' => $runtimeName, - 'execute' => $template['body']['permissions'], - 'entrypoint' => $entrypoint, - 'events' => $template['body']['events'], - 'schedule' => $template['body']['cron'], - 'timeout' => $template['body']['timeout'], - 'commands' => $commands, - 'scopes' => $template['body']['scopes'], - 'templateRepository' => $template['body']['providerRepositoryId'], - 'templateOwner' => $template['body']['providerOwner'], - 'templateRootDirectory' => $rootDirectory, - 'templateVersion' => $template['body']['providerVersion'], - ]); + // If this fails, the template has variables, and this test needs to be updated + $this->assertEmpty($starterTemplate['body']['variables']); + $function = $this->createFunction( + [ + 'functionId' => ID::unique(), + 'name' => $starterTemplate['body']['name'], + 'runtime' => 'php-8.0', + 'execute' => $starterTemplate['body']['permissions'], + 'entrypoint' => $phpRuntime['entrypoint'], + 'events' => $starterTemplate['body']['events'], + 'schedule' => $starterTemplate['body']['cron'], + 'timeout' => $starterTemplate['body']['timeout'], + 'commands' => $phpRuntime['commands'], + 'scopes' => $starterTemplate['body']['scopes'], + 'templateRepository' => $starterTemplate['body']['providerRepositoryId'], + 'templateOwner' => $starterTemplate['body']['providerOwner'], + 'templateRootDirectory' => $phpRuntime['providerRootDirectory'], + 'templateVersion' => $starterTemplate['body']['providerVersion'], + ] + ); + $functionId = $function['body']['$id']; $this->assertEquals(201, $function['headers']['status-code']); $this->assertNotEmpty($function['body']['$id']); - $functionId = $function['body']['$id']; - - // List deployments so we can await deployment build - $deployments = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/deployments', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], []); - + $deployments = $this->listDeployments($functionId); $this->assertEquals(200, $deployments['headers']['status-code']); $this->assertEquals(1, $deployments['body']['total']); - $this->assertNotEmpty($deployments['body']['deployments'][0]['$id']); - $this->assertEquals(0, $deployments['body']['deployments'][0]['size']); - $deploymentId = $deployments['body']['deployments'][0]['$id']; + $lastDeployment = $deployments['body']['deployments'][0]; + $this->assertNotEmpty($lastDeployment['$id']); + $this->assertEquals(0, $lastDeployment['size']); - // Wait for deployment build to finish - // Deployment is automatically activated - $this->assertDeployment($function['body']['$id'], $deploymentId); + $deploymentId = $lastDeployment['$id']; + $this->assertDeploymentStatus($function['body']['$id'], $deploymentId, 'ready'); - $deployments = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/deployments/' . $deploymentId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], []); - $this->assertGreaterThan(0, $deployments['body']['size']); - - $function = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId, array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], $this->getHeaders()), []); + $deployment = $this->getDeployment($function['body']['$id'], $deploymentId); + $this->assertGreaterThan(0, $deployment['body']['size']); + $function = $this->getFunction($functionId); $this->assertEquals(200, $function['headers']['status-code']); $this->assertEquals($deploymentId, $function['body']['deployment']); - // Execute function to ensure starter code is used - // Also tests if dynamic keys works as expected - $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'path' => '/ping' + // Test starter code is used and that dynamic keys work + $execution = $this->createExecution($functionId, [ + 'path' => '/ping', ]); - $this->assertEquals(201, $execution['headers']['status-code']); $this->assertEquals("completed", $execution['body']['status']); $this->assertEquals(200, $execution['body']['responseStatusCode']); $this->assertEquals("Pong", $execution['body']['responseBody']); $this->assertEmpty($execution['body']['errors']); - // Get users to ensure execution logged correct total users + // Test execution logged correct total users $users = $this->client->call(Client::METHOD_GET, '/users', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'], ], $this->getHeaders()), []); - $this->assertEquals(200, $users['headers']['status-code']); $this->assertIsInt($users['body']['total']); - $totalusers = $users['body']['total']; - - $this->assertStringContainsString("Total users: " . $totalusers, $execution['body']['logs']); + $totalUsers = $users['body']['total']; + $this->assertStringContainsString("Total users: " . $totalUsers, $execution['body']['logs']); // Execute function again but async - $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + $execution = $this->createExecution($functionId, [ 'path' => '/ping', 'async' => true ]); - + $executionId = $execution['body']['$id']; $this->assertEquals(202, $execution['headers']['status-code']); $this->assertNotEmpty($execution['body']['$id']); $this->assertEquals('waiting', $execution['body']['status']); - $executionId = $execution['body']['$id']; - // Wait for async execuntion to finish - sleep(5); + $this->assertEventually(function () use ($functionId, $executionId, $totalUsers) { + $execution = $this->getExecution($functionId, $executionId); - // Ensure execution was successful - $execution = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions/' . $executionId, array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), []); + $this->assertEquals('completed', $execution['body']['status']); + $this->assertEquals(200, $execution['headers']['status-code']); + $this->assertEquals("completed", $execution['body']['status']); + $this->assertEquals(200, $execution['body']['responseStatusCode']); + $this->assertEmpty($execution['body']['responseBody']); + $this->assertEmpty($execution['body']['errors']); + $this->assertStringContainsString("Total users: " . $totalUsers, $execution['body']['logs']); + }, 100000, 250); - $this->assertEquals(200, $execution['headers']['status-code']); - $this->assertEquals("completed", $execution['body']['status']); - $this->assertEquals(200, $execution['body']['responseStatusCode']); - $this->assertEmpty($execution['body']['responseBody']); - $this->assertEmpty($execution['body']['errors']); - $this->assertStringContainsString("Total users: " . $totalusers, $execution['body']['logs']); - - // Cleanup : Delete function - $response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], []); - - $this->assertEquals(204, $response['headers']['status-code']); + $function = $this->deleteFunction($functionId); + $this->assertEquals(204, $function['headers']['status-code']); } /** @@ -615,15 +529,11 @@ class FunctionsCustomServerTest extends Scope /** * Test for SUCCESS */ - $folder = 'php'; - $code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz"; - $this->packageCode($folder); - $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $data['functionId'] . '/deployments', array_merge([ 'content-type' => 'multipart/form-data', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'code' => new CURLFile($code, 'application/x-gzip', \basename($code)), + 'code' => $this->packageFunction('php'), 'activate' => true ]); @@ -640,7 +550,7 @@ class FunctionsCustomServerTest extends Scope 'content-type' => 'multipart/form-data', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'code' => new CURLFile($code, 'application/x-gzip', \basename($code)), + 'code' => $this->packageFunction('php'), 'activate' => 'false' ]); @@ -675,16 +585,11 @@ class FunctionsCustomServerTest extends Scope */ public function testCancelDeploymentBuild($data): void { - // Create a new deployment to cancel - $folder = 'php'; - $code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz"; - $this->packageCode($folder); - $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $data['functionId'] . '/deployments', array_merge([ 'content-type' => 'multipart/form-data', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'code' => new CURLFile($code, 'application/x-gzip', \basename($code)), + 'code' => $this->packageFunction('php'), 'activate' => true ]); @@ -1406,16 +1311,6 @@ class FunctionsCustomServerTest extends Scope $executionId = $execution['body']['$id'] ?? ''; $this->assertEquals(202, $execution['headers']['status-code']); - $this->assertEventually(function () use ($data, $executionId) { - $execution = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/executions/' . $executionId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ]); - - $this->assertEquals(200, $execution['headers']['status-code']); - $this->assertEquals('waiting', $execution['body']['status']); - }, 10000, 500); - $execution = $this->client->call(Client::METHOD_DELETE, '/functions/' . $data['functionId'] . '/executions/' . $executionId, [ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -1580,87 +1475,41 @@ class FunctionsCustomServerTest extends Scope public function testTimeout() { - $name = 'php-8.0'; - $entrypoint = 'index.php'; - $timeout = 15; - $folder = 'timeout'; - $code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz"; - $this->packageCode($folder); - - $function = $this->client->call(Client::METHOD_POST, '/functions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'functionId' => ID::unique(), - 'name' => 'Test ' . $name, - 'runtime' => $name, - 'entrypoint' => $entrypoint, + $function = $this->createFunction([ + 'name' => 'timeout-php-8.0', + 'runtime' => 'php-8.0', + 'entrypoint' => 'index.php', 'events' => [], - 'schedule' => '* * * * *', // execute every minute - 'timeout' => $timeout, + 'schedule' => '*/10 * * * *', // Execute every 10 seconds + 'timeout' => 15, ]); - - $functionId = $function['body']['$id'] ?? ''; - + $functionId = $function['body']['id']; $this->assertEquals(201, $function['headers']['status-code']); - $this->assertEquals('* * * * *', $function['body']['schedule']); - $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', array_merge([ - 'content-type' => 'multipart/form-data', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'entrypoint' => $entrypoint, - 'code' => new CURLFile($code, 'application/x-gzip', basename($code)), - 'activate' => true, - ]); - - $deploymentId = $deployment['body']['$id'] ?? ''; - - $this->assertEquals(202, $deployment['headers']['status-code']); - - $this->assertDeployment($functionId, $deploymentId); - - $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'async' => true, - ]); + $deployment = $this->createDeployment($functionId, 'php'); + $deploymentId = $deployment['body']['$id']; + $this->assertDeploymentStatus($functionId, $deploymentId, 'ready'); + $execution = $this->createExecution($functionId, ['async' => true]); $executionId = $execution['body']['$id'] ?? ''; - $this->assertEquals(202, $execution['headers']['status-code']); - $executionData = null; + $this->assertEventually(function () use ($functionId, $executionId) { + $execution = $this->getExecution($functionId, $executionId); - $this->assertEventually(function () use ($functionId, $executionId, &$executionData) { - $executionData = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions/' . $executionId, array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); + $this->assertEquals(200, $execution['headers']['status-code']); + $this->assertEquals('failed', $execution['body']['status']); + $this->assertEquals('failed', $execution['body']['status']); + $this->assertEquals(500, $execution['body']['responseStatusCode']); + $this->assertGreaterThan(2, $execution['body']['duration']); + $this->assertLessThan(20, $execution['body']['duration']); + $this->assertEquals('', $execution['body']['responseBody']); + $this->assertEquals('', $execution['body']['logs']); + $this->assertStringContainsString('timed out', $execution['body']['errors']); + }, 20000, 500); - $this->assertEquals(200, $executionData['headers']['status-code']); - $this->assertEquals('failed', $executionData['body']['status']); - - if ($executionData['body']['status'] !== 'failed') { - } - }, 30000, 1000); - - $this->assertEquals('failed', $executionData['body']['status']); - $this->assertEquals(500, $executionData['body']['responseStatusCode']); - $this->assertGreaterThan(2, $executionData['body']['duration']); - $this->assertLessThan(20, $executionData['body']['duration']); - $this->assertEquals('', $executionData['body']['responseBody']); - $this->assertEquals('', $executionData['body']['logs']); - $this->assertStringContainsString('timed out', $executionData['body']['errors']); - - $scheduledExecutionData = null; - - $this->assertEventually(function () use ($functionId, &$scheduledExecutionData) { - $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + $this->assertEventually(function () use ($functionId) { + $executions = $this->listExecutions($functionId, [ 'queries' => [ Query::equal('trigger', ['schedule'])->toString(), ], @@ -1669,19 +1518,12 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals(200, $executions['headers']['status-code']); $this->assertEquals(1, $executions['body']['total']); - $scheduledExecutionData = $executions['body']['executions'][0]; - }, 140000, 1000); // Wait up to 140 seconds, checking every 1 second + $lastExecution = $executions['body']['executions'][0]; + $this->assertEquals('schedule', $lastExecution['trigger']); + }, 20000, 500); - $this->assertEquals('schedule', $scheduledExecutionData['trigger']); - - // Cleanup : Delete function - $response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], []); - - $this->assertEquals(204, $response['headers']['status-code']); + $function = $this->deleteFunction($functionId); + $this->assertEquals(204, $function['headers']['status-code']); } From af509f3479a25def65685e090d33bf4ee6cd41f1 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Thu, 19 Sep 2024 14:17:38 +0100 Subject: [PATCH 04/33] fix: flakyness --- .../e2e/Services/Functions/FunctionsBase.php | 78 +++++++------------ .../Functions/FunctionsConsoleClientTest.php | 24 ++++-- .../Functions/FunctionsCustomClientTest.php | 10 ++- 3 files changed, 51 insertions(+), 61 deletions(-) diff --git a/tests/e2e/Services/Functions/FunctionsBase.php b/tests/e2e/Services/Functions/FunctionsBase.php index 92210a3027..cdb4781e5e 100644 --- a/tests/e2e/Services/Functions/FunctionsBase.php +++ b/tests/e2e/Services/Functions/FunctionsBase.php @@ -21,27 +21,26 @@ trait FunctionsBase 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'], ]), $params); - $functionId = $function['body']['$id']; + $this->assertEquals($function['headers']['status-code'], 201, 'Setup function failed with status code: ' . $function['headers']['status-code'] . ' and response: ' . json_encode($function['body'])); - $this->assertEquals($function['headers']['status-code'], 201); + $functionId = $function['body']['$id']; return $functionId; } protected function setupDeployment(string $functionId, mixed $params): string { $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', array_merge([ - 'content-type' => 'application/json', + 'content-type' => 'multipart/form-data', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'], ]), $params); + $this->assertEquals($deployment['headers']['status-code'], 202, 'Setup deployment failed with status code: ' . $deployment['headers']['status-code'] . ' and response: ' . json_encode($deployment['body'])); $deploymentId = $deployment['body']['$id']; - $this->assertEquals($deployment['headers']['status-code'], 202); - $this->assertEventually(function () use ($functionId, $deploymentId) { $deployment = $this->getDeployment($functionId, $deploymentId); $this->assertEquals('completed', $deployment['body']['status']); - }, 200000, 500); + }, 50000, 500); return $deploymentId; } @@ -57,40 +56,27 @@ trait FunctionsBase $this->assertEquals($function['headers']['status-code'], 204); } - protected function createFunction(mixed $params) + protected function createFunction(mixed $params): mixed { $function = $this->client->call(Client::METHOD_POST, '/functions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), $params); - $functionId = $function['body']['$id']; - - if (empty($variables)) { - return $function; - } - - $function = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId, array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); return $function; } - protected function createVariable($functionId, $key, $value) + protected function createVariable(string $functionId, mixed $params): mixed { $variable = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/variables', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'key' => $key, - 'value' => $value - ]); + ], $this->getHeaders()), $params); return $variable; } - protected function getFunction($functionId) + protected function getFunction(string $functionId): mixed { $function = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId, array_merge([ 'content-type' => 'application/json', @@ -100,7 +86,7 @@ trait FunctionsBase return $function; } - protected function getDeployment($functionId, $deploymentId) + protected function getDeployment(string $functionId, string $deploymentId): mixed { $deployment = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/deployments/' . $deploymentId, array_merge([ 'content-type' => 'application/json', @@ -120,12 +106,22 @@ trait FunctionsBase return $execution; } + protected function listFunctions($params = []) + { + $functions = $this->client->call(Client::METHOD_GET, '/functions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), $params); + + return $functions; + } + protected function listDeployments($functionId, $params = []) { $deployments = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/deployments', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders(), $params)); + ], $this->getHeaders()), $params); return $deployments; } @@ -135,19 +131,17 @@ trait FunctionsBase $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders(), $params)); + ], $this->getHeaders()), $params); return $executions; } - protected function packageFunction(string $folder = 'php') + protected function packageFunction(string $folder = 'php'): CURLFile { $code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz"; - Console::execute('cd ' . realpath(__DIR__ . "/../../../resources/functions") . "/$folder && tar --exclude code.tar.gz -czf code.tar.gz .", '', $this->stdout, $this->stderr); - if (!file_exists($code)) { - throw new \Exception('Failed to create code package. ' . $code . ' does not exist.'); + Console::execute('cd ' . realpath(__DIR__ . "/../../../resources/functions") . "/$folder && tar --exclude code.tar.gz -czf code.tar.gz .", '', $this->stdout, $this->stderr); } if (filesize($code) > 1024 * 1024 * 5) { throw new \Exception('Code package is too large. Use the chunked upload method instead.'); @@ -158,36 +152,22 @@ trait FunctionsBase protected function createDeployment( string $functionId, - mixed $params, - bool $cli = false, - bool $admin = false + mixed $params ) { - $authHeaders = $this->getHeaders(); - - if ($admin) { - $authHeaders = [ - 'x-appwrite-key' => $this->getProject()['apiKey'], - ]; - } - if ($cli) { - $authHeaders[] = 'x-sdk-language: cli'; - } - - $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', [ + $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', array_merge([ 'content-type' => 'multipart/form-data', 'x-appwrite-project' => $this->getProject()['$id'], - - ], $params); + ], $this->getHeaders()), $params); return $deployment; } - protected function getFunctionUsage($functionId, $params) + protected function getFunctionUsage(string $functionId, mixed $params): mixed { $usage = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/usage', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders(), $params)); + ], $this->getHeaders()), $params); return $usage; } diff --git a/tests/e2e/Services/Functions/FunctionsConsoleClientTest.php b/tests/e2e/Services/Functions/FunctionsConsoleClientTest.php index 6ee5a1085d..cbfca09564 100644 --- a/tests/e2e/Services/Functions/FunctionsConsoleClientTest.php +++ b/tests/e2e/Services/Functions/FunctionsConsoleClientTest.php @@ -105,8 +105,10 @@ class FunctionsConsoleClientTest extends Scope */ $variable = $this->createVariable( $data['functionId'], - 'APP_TEST', - 'TESTINGVALUE' + [ + 'key' => 'APP_TEST', + 'value' => 'TESTINGVALUE' + ] ); $variableId = $variable['body']['$id']; $this->assertEquals(201, $variable['headers']['status-code']); @@ -117,24 +119,30 @@ class FunctionsConsoleClientTest extends Scope // Test for duplicate key $variable = $this->createVariable( $data['functionId'], - 'APP_TEST', - 'ANOTHER_TESTINGVALUE' + [ + 'key' => 'APP_TEST', + 'value' => 'ANOTHERTESTINGVALUE' + ] ); $this->assertEquals(409, $variable['headers']['status-code']); // Test for invalid key $variable = $this->createVariable( $data['functionId'], - str_repeat("A", 256), - 'TESTINGVALUE' + [ + 'key' => str_repeat("A", 256), + 'value' => 'TESTINGVALUE' + ] ); $this->assertEquals(400, $variable['headers']['status-code']); // Test for invalid value $variable = $this->createVariable( $data['functionId'], - 'LONGKEY', - str_repeat("#", 8193) + [ + 'key' => 'LONGKEY', + 'value' => str_repeat("#", 8193), + ] ); $this->assertEquals(400, $variable['headers']['status-code']); diff --git a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php index 872249085e..1dc81c8171 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php @@ -15,7 +15,7 @@ class FunctionsCustomClientTest extends Scope use ProjectCustom; use SideClient; - public function testCreate() + public function testCreateFunction() { /** * Test for FAILURE @@ -80,7 +80,7 @@ class FunctionsCustomClientTest extends Scope $this->assertNotEmpty($asyncExecution['logs']); $this->assertNotEmpty($asyncExecution['errors']); $this->assertGreaterThan(0, $asyncExecution['duration']); - }, 100000, 250); + }, 10000, 250); $this->cleanupFunction($functionId); } @@ -106,7 +106,7 @@ class FunctionsCustomClientTest extends Scope // Schedule execution for the future \date_default_timezone_set('UTC'); - $futureTime = (new \DateTime())->add(new \DateInterval('PT30S')); // 30 seconds from now + $futureTime = (new \DateTime())->add(new \DateInterval('PT15S')); // 15 seconds from now $futureTime->setTime($futureTime->format('H'), $futureTime->format('i'), 0, 0); $execution = $this->createExecution($functionId, [ @@ -126,6 +126,8 @@ class FunctionsCustomClientTest extends Scope $this->assertEquals('/custom-path', $execution['body']['requestPath']); $this->assertCount(0, $execution['body']['requestHeaders']); + \sleep(15); + $this->assertEventually(function () use ($functionId, $executionId) { $execution = $this->getExecution($functionId, $executionId); $this->assertEquals('completed', $execution['body']['status']); @@ -141,7 +143,7 @@ class FunctionsCustomClientTest extends Scope $this->assertStringContainsString('user-is-' . $this->getUser()['$id'], $execution['body']['logs']); $this->assertStringContainsString('jwt-is-valid', $execution['body']['logs']); $this->assertGreaterThan(0, $execution['body']['duration']); - }, 40000, 2500); + }, 10000, 250); /* Test for FAILURE */ // Schedule synchronous execution From 964b616e05b3c6ee2c1ba3b03b276deb95c59ee8 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Thu, 19 Sep 2024 20:21:00 +0100 Subject: [PATCH 05/33] fix: refactor custom server test --- .../e2e/Services/Functions/FunctionsBase.php | 16 +- .../Functions/FunctionsCustomClientTest.php | 37 +- .../Functions/FunctionsCustomServerTest.php | 1582 +++++------------ 3 files changed, 481 insertions(+), 1154 deletions(-) diff --git a/tests/e2e/Services/Functions/FunctionsBase.php b/tests/e2e/Services/Functions/FunctionsBase.php index cdb4781e5e..730c7bff48 100644 --- a/tests/e2e/Services/Functions/FunctionsBase.php +++ b/tests/e2e/Services/Functions/FunctionsBase.php @@ -21,7 +21,7 @@ trait FunctionsBase 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'], ]), $params); - $this->assertEquals($function['headers']['status-code'], 201, 'Setup function failed with status code: ' . $function['headers']['status-code'] . ' and response: ' . json_encode($function['body'])); + $this->assertEquals($function['headers']['status-code'], 201, 'Setup function failed with status code: ' . $function['headers']['status-code'] . ' and response: ' . json_encode($function['body'], JSON_PRETTY_PRINT)); $functionId = $function['body']['$id']; return $functionId; @@ -34,12 +34,16 @@ trait FunctionsBase 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'], ]), $params); - $this->assertEquals($deployment['headers']['status-code'], 202, 'Setup deployment failed with status code: ' . $deployment['headers']['status-code'] . ' and response: ' . json_encode($deployment['body'])); - $deploymentId = $deployment['body']['$id']; + $this->assertEquals($deployment['headers']['status-code'], 202, 'Setup deployment failed with status code: ' . $deployment['headers']['status-code'] . ' and response: ' . json_encode($deployment['body'], JSON_PRETTY_PRINT)); + $deploymentId = $deployment['body']['$id'] ?? ''; $this->assertEventually(function () use ($functionId, $deploymentId) { - $deployment = $this->getDeployment($functionId, $deploymentId); - $this->assertEquals('completed', $deployment['body']['status']); + $deployment = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/deployments/' . $deploymentId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ])); + $this->assertEquals('ready', $deployment['body']['status']); }, 50000, 500); return $deploymentId; @@ -182,7 +186,7 @@ trait FunctionsBase return $template; } - protected function createExecution(string $functionId, mixed $params) + protected function createExecution(string $functionId, mixed $params = []) { $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([ 'content-type' => 'application/json', diff --git a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php index 1dc81c8171..dd950ca655 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php @@ -48,7 +48,7 @@ class FunctionsCustomClientTest extends Scope 'users.*.create', 'users.*.delete', ], - 'schedule' => '* * * * *', // Execute every minute + 'schedule' => '* * * * *', // Execute every 60 seconds 'timeout' => 10, ]); $this->setupDeployment($functionId, [ @@ -57,18 +57,30 @@ class FunctionsCustomClientTest extends Scope 'activate' => true ]); - $execution = $this->createExecution($functionId, [ - 'async' => false, + // Deny create async execution as guest + $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], [ + 'async' => true, ]); $this->assertEquals(401, $execution['headers']['status-code']); + // Allow create async execution as user $execution = $this->createExecution($functionId, [ 'async' => true, ]); $this->assertEquals(202, $execution['headers']['status-code']); + // Wait for scheduled execution + \sleep(60); + $this->assertEventually(function () use ($functionId) { - $executions = $this->listExecutions($functionId); + $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['key'], + ]); $this->assertEquals(200, $executions['headers']['status-code']); $this->assertCount(2, $executions['body']['executions']); @@ -80,7 +92,7 @@ class FunctionsCustomClientTest extends Scope $this->assertNotEmpty($asyncExecution['logs']); $this->assertNotEmpty($asyncExecution['errors']); $this->assertGreaterThan(0, $asyncExecution['duration']); - }, 10000, 250); + }, 10000, 500); $this->cleanupFunction($functionId); } @@ -97,6 +109,7 @@ class FunctionsCustomClientTest extends Scope 'runtime' => 'php-8.0', 'entrypoint' => 'index.php', 'timeout' => 10, + 'logging' => true, ]); $this->setupDeployment($functionId, [ 'entrypoint' => 'index.php', @@ -106,7 +119,7 @@ class FunctionsCustomClientTest extends Scope // Schedule execution for the future \date_default_timezone_set('UTC'); - $futureTime = (new \DateTime())->add(new \DateInterval('PT15S')); // 15 seconds from now + $futureTime = (new \DateTime())->add(new \DateInterval('PT2M')); // 2 minute in the future $futureTime->setTime($futureTime->format('H'), $futureTime->format('i'), 0, 0); $execution = $this->createExecution($functionId, [ @@ -120,17 +133,18 @@ class FunctionsCustomClientTest extends Scope ] ]); $executionId = $execution['body']['$id']; + $this->assertEquals(202, $execution['headers']['status-code']); $this->assertEquals('scheduled', $execution['body']['status']); $this->assertEquals('PATCH', $execution['body']['requestMethod']); $this->assertEquals('/custom-path', $execution['body']['requestPath']); $this->assertCount(0, $execution['body']['requestHeaders']); - \sleep(15); + \sleep(120); $this->assertEventually(function () use ($functionId, $executionId) { $execution = $this->getExecution($functionId, $executionId); - $this->assertEquals('completed', $execution['body']['status']); + $this->assertEquals(200, $execution['headers']['status-code']); $this->assertEquals(200, $execution['body']['responseStatusCode']); $this->assertEquals('completed', $execution['body']['status']); @@ -143,7 +157,7 @@ class FunctionsCustomClientTest extends Scope $this->assertStringContainsString('user-is-' . $this->getUser()['$id'], $execution['body']['logs']); $this->assertStringContainsString('jwt-is-valid', $execution['body']['logs']); $this->assertGreaterThan(0, $execution['body']['duration']); - }, 10000, 250); + }, 10000, 500); /* Test for FAILURE */ // Schedule synchronous execution @@ -377,9 +391,11 @@ class FunctionsCustomClientTest extends Scope $templates = $this->client->call(Client::METHOD_GET, '/functions/templates', array_merge([ 'content-type' => 'application/json', ], $this->getHeaders())); + $this->assertEquals(200, $templates['headers']['status-code']); $this->assertGreaterThan(0, $templates['body']['total']); $this->assertIsArray($templates['body']['templates']); + foreach ($templates['body']['templates'] as $template) { $this->assertArrayHasKey('name', $template); $this->assertArrayHasKey('id', $template); @@ -389,7 +405,6 @@ class FunctionsCustomClientTest extends Scope $this->assertArrayHasKey('vcsProvider', $template); $this->assertArrayHasKey('runtimes', $template); $this->assertArrayHasKey('variables', $template); - $this->assertArrayHasKey('scopes', $template); } // List templates with pagination @@ -434,9 +449,11 @@ class FunctionsCustomClientTest extends Scope $this->assertEquals(5, $templates['body']['total']); $this->assertIsArray($templates['body']['templates']); $this->assertArrayHasKey('runtimes', $templates['body']['templates'][0]); + foreach ($templates['body']['templates'] as $template) { $this->assertContains($template['useCases'][0], ['databases']); } + $this->assertContains('node-16.0', array_column($templates['body']['templates'][0]['runtimes'], 'name')); /** diff --git a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php index 8fe4aedca7..80f34d6d34 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php @@ -10,27 +10,27 @@ use Tests\E2E\Scopes\ProjectCustom; use Tests\E2E\Scopes\Scope; use Tests\E2E\Scopes\SideServer; use Utopia\App; +use Utopia\CLI\Console; use Utopia\Database\Document; use Utopia\Database\Helpers\ID; use Utopia\Database\Helpers\Role; use Utopia\Database\Query; use Utopia\Database\Validator\Datetime as DatetimeValidator; +use function PHPSTORM_META\map; + class FunctionsCustomServerTest extends Scope { use FunctionsBase; use ProjectCustom; use SideServer; - public function testCreate(): array + public function testCreateFunction(): array { /** * Test for SUCCESS */ - $response1 = $this->client->call(Client::METHOD_POST, '/functions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + $function = $this->createFunction([ 'functionId' => ID::unique(), 'name' => 'Test', 'runtime' => 'php-8.0', @@ -42,162 +42,115 @@ class FunctionsCustomServerTest extends Scope 'schedule' => '0 0 1 1 *', 'timeout' => 10, ]); - - $functionId = $response1['body']['$id'] ?? ''; - - $this->assertEquals(201, $response1['headers']['status-code']); - $this->assertNotEmpty($response1['body']['$id']); - $this->assertEquals('Test', $response1['body']['name']); - $this->assertEquals('php-8.0', $response1['body']['runtime']); + $functionId = $functionId = $function['body']['$id'] ?? ''; $dateValidator = new DatetimeValidator(); - $this->assertEquals(true, $dateValidator->isValid($response1['body']['$createdAt'])); - $this->assertEquals(true, $dateValidator->isValid($response1['body']['$updatedAt'])); - $this->assertEquals('', $response1['body']['deployment']); + $this->assertEquals(201, $function['headers']['status-code']); + $this->assertNotEmpty($function['body']['$id']); + $this->assertEquals('Test', $function['body']['name']); + $this->assertEquals('php-8.0', $function['body']['runtime']); + $this->assertEquals(true, $dateValidator->isValid($function['body']['$createdAt'])); + $this->assertEquals(true, $dateValidator->isValid($function['body']['$updatedAt'])); + $this->assertEquals('', $function['body']['deployment']); $this->assertEquals([ 'buckets.*.create', 'buckets.*.delete', - ], $response1['body']['events']); - $this->assertEquals('0 0 1 1 *', $response1['body']['schedule']); - $this->assertEquals(10, $response1['body']['timeout']); + ], $function['body']['events']); + $this->assertEquals('0 0 1 1 *', $function['body']['schedule']); + $this->assertEquals(10, $function['body']['timeout']); - /** Create Variables */ - $variable = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/variables', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + $variable = $this->createVariable($functionId, [ 'key' => 'funcKey1', 'value' => 'funcValue1', ]); - - $variable2 = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/variables', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + $variable2 = $this->createVariable($functionId, [ 'key' => 'funcKey2', 'value' => 'funcValue2', ]); - - $variable3 = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/variables', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + $variable3 = $this->createVariable($functionId, [ 'key' => 'funcKey3', 'value' => 'funcValue3', ]); - $this->assertEquals(201, $variable['headers']['status-code']); $this->assertEquals(201, $variable2['headers']['status-code']); $this->assertEquals(201, $variable3['headers']['status-code']); - /** - * Test for FAILURE - */ - return [ 'functionId' => $functionId, ]; } /** - * @depends testCreate + * @depends testCreateFunction */ - public function testList(array $data): array + public function testListFunctions(array $data): array { /** * Test for SUCCESS */ - - /** - * Test search queries - */ - $response = $this->client->call(Client::METHOD_GET, '/functions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + // Test search id + $functions = $this->listFunctions([ 'search' => $data['functionId'] ]); + $this->assertEquals($functions['headers']['status-code'], 200); + $this->assertCount(1, $functions['body']['functions']); + $this->assertEquals($functions['body']['functions'][0]['name'], 'Test'); - $this->assertEquals($response['headers']['status-code'], 200); - $this->assertCount(1, $response['body']['functions']); - $this->assertEquals($response['body']['functions'][0]['name'], 'Test'); - - $response = $this->client->call(Client::METHOD_GET, '/functions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + // Test pagination limit + $functions = $this->listFunctions([ 'queries' => [ Query::limit(1)->toString(), ], ]); + $this->assertEquals($functions['headers']['status-code'], 200); + $this->assertCount(1, $functions['body']['functions']); - $this->assertEquals($response['headers']['status-code'], 200); - $this->assertCount(1, $response['body']['functions']); - - $response = $this->client->call(Client::METHOD_GET, '/functions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + // Test pagination offset + $functions = $this->listFunctions([ 'queries' => [ Query::offset(1)->toString(), ], ]); + $this->assertEquals($functions['headers']['status-code'], 200); + $this->assertCount(0, $functions['body']['functions']); - $this->assertEquals($response['headers']['status-code'], 200); - $this->assertCount(0, $response['body']['functions']); - - $response = $this->client->call(Client::METHOD_GET, '/functions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + // Test filter enabled + $functions = $this->listFunctions([ 'queries' => [ Query::equal('enabled', [true])->toString(), ], ]); + $this->assertEquals($functions['headers']['status-code'], 200); + $this->assertCount(1, $functions['body']['functions']); - $this->assertEquals($response['headers']['status-code'], 200); - $this->assertCount(1, $response['body']['functions']); - - $response = $this->client->call(Client::METHOD_GET, '/functions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + // Test filter disabled + $functions = $this->listFunctions([ 'queries' => [ Query::equal('enabled', [false])->toString(), ], ]); + $this->assertEquals($functions['headers']['status-code'], 200); + $this->assertCount(0, $functions['body']['functions']); - $this->assertEquals($response['headers']['status-code'], 200); - $this->assertCount(0, $response['body']['functions']); - - $response = $this->client->call(Client::METHOD_GET, '/functions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + // Test search name + $functions = $this->listFunctions([ 'search' => 'Test' ]); + $this->assertEquals($functions['headers']['status-code'], 200); + $this->assertCount(1, $functions['body']['functions']); + $this->assertEquals($functions['body']['functions'][0]['$id'], $data['functionId']); - $this->assertEquals($response['headers']['status-code'], 200); - $this->assertCount(1, $response['body']['functions']); - $this->assertEquals($response['body']['functions'][0]['$id'], $data['functionId']); - - $response = $this->client->call(Client::METHOD_GET, '/functions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + // Test search runtime + $functions = $this->listExecutions([ 'search' => 'php-8.0' ]); - - $this->assertEquals($response['headers']['status-code'], 200); - $this->assertCount(1, $response['body']['functions']); - $this->assertEquals($response['body']['functions'][0]['$id'], $data['functionId']); + $this->assertEquals($functions['headers']['status-code'], 200); + $this->assertCount(1, $functions['body']['functions']); + $this->assertEquals($functions['body']['functions'][0]['$id'], $data['functionId']); /** * Test pagination */ - $response = $this->client->call(Client::METHOD_POST, '/functions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + $functionId = $this->setupFunction([ 'functionId' => ID::unique(), 'name' => 'Test 2', 'runtime' => 'php-8.0', @@ -209,42 +162,8 @@ class FunctionsCustomServerTest extends Scope 'schedule' => '0 0 1 1 *', 'timeout' => 10, ]); - $this->assertNotEmpty($response['body']['$id']); - - /** Create Variables */ - $variable = $this->client->call(Client::METHOD_POST, '/functions/' . $response['body']['$id'] . '/variables', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'key' => 'funcKey1', - 'value' => 'funcValue1', - ]); - - $variable2 = $this->client->call(Client::METHOD_POST, '/functions/' . $response['body']['$id'] . '/variables', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'key' => 'funcKey2', - 'value' => 'funcValue2', - ]); - - $variable3 = $this->client->call(Client::METHOD_POST, '/functions/' . $response['body']['$id'] . '/variables', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'key' => 'funcKey3', - 'value' => 'funcValue3', - ]); - - $this->assertEquals(201, $variable['headers']['status-code']); - $this->assertEquals(201, $variable2['headers']['status-code']); - $this->assertEquals(201, $variable3['headers']['status-code']); - - $functions = $this->client->call(Client::METHOD_GET, '/functions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); + $functions = $this->listFunctions(); $this->assertEquals($functions['headers']['status-code'], 200); $this->assertEquals($functions['body']['total'], 2); $this->assertIsArray($functions['body']['functions']); @@ -252,87 +171,67 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals($functions['body']['functions'][0]['name'], 'Test'); $this->assertEquals($functions['body']['functions'][1]['name'], 'Test 2'); - $response = $this->client->call(Client::METHOD_GET, '/functions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + $functions = $this->listFunctions([ 'queries' => [ Query::cursorAfter(new Document(['$id' => $functions['body']['functions'][0]['$id']]))->toString(), ], ]); + $this->assertEquals($functions['headers']['status-code'], 200); + $this->assertCount(1, $functions['body']['functions']); + $this->assertEquals($functions['body']['functions'][0]['name'], 'Test 2'); - $this->assertEquals($response['headers']['status-code'], 200); - $this->assertCount(1, $response['body']['functions']); - $this->assertEquals($response['body']['functions'][0]['name'], 'Test 2'); - - $response = $this->client->call(Client::METHOD_GET, '/functions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + $functions = $this->listFunctions([ 'queries' => [ Query::cursorBefore(new Document(['$id' => $functions['body']['functions'][1]['$id']]))->toString(), ], ]); - - $this->assertEquals($response['headers']['status-code'], 200); - $this->assertCount(1, $response['body']['functions']); - $this->assertEquals($response['body']['functions'][0]['name'], 'Test'); + $this->assertEquals($functions['headers']['status-code'], 200); + $this->assertCount(1, $functions['body']['functions']); + $this->assertEquals($functions['body']['functions'][0]['name'], 'Test'); /** * Test for FAILURE */ - $response = $this->client->call(Client::METHOD_GET, '/functions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + $functions = $this->listFunctions([ 'queries' => [ Query::cursorAfter(new Document(['$id' => 'unknown']))->toString(), ], ]); - - $this->assertEquals($response['headers']['status-code'], 400); + $this->assertEquals($functions['headers']['status-code'], 400); return $data; } /** - * @depends testList + * @depends testListFunctions */ - public function testGet(array $data): array + public function testGetFunction(array $data): array { /** * Test for SUCCESS */ - $function = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'], array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - + $function = $this->getFunction($data['functionId']); $this->assertEquals($function['headers']['status-code'], 200); $this->assertEquals($function['body']['name'], 'Test'); /** * Test for FAILURE */ - $function = $this->client->call(Client::METHOD_GET, '/functions/x', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - + $function = $this->getFunction('x'); $this->assertEquals($function['headers']['status-code'], 404); return $data; } /** - * @depends testGet + * @depends testGetFunction */ - public function testUpdate($data): array + public function testUpdateFunction($data): array { /** * Test for SUCCESS */ - $response1 = $this->client->call(Client::METHOD_PUT, '/functions/' . $data['functionId'], array_merge([ + $function = $this->client->call(Client::METHOD_PUT, '/functions/' . $data['functionId'], array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -347,36 +246,25 @@ class FunctionsCustomServerTest extends Scope 'entrypoint' => 'index.php', ]); - $this->assertEquals(200, $response1['headers']['status-code']); - $this->assertNotEmpty($response1['body']['$id']); - $this->assertEquals('Test1', $response1['body']['name']); + $this->assertEquals(200, $function['headers']['status-code']); + $this->assertNotEmpty($function['body']['$id']); + $this->assertEquals('Test1', $function['body']['name']); $dateValidator = new DatetimeValidator(); - $this->assertEquals(true, $dateValidator->isValid($response1['body']['$createdAt'])); - $this->assertEquals(true, $dateValidator->isValid($response1['body']['$updatedAt'])); - $this->assertEquals('', $response1['body']['deployment']); + $this->assertEquals(true, $dateValidator->isValid($function['body']['$createdAt'])); + $this->assertEquals(true, $dateValidator->isValid($function['body']['$updatedAt'])); + $this->assertEquals('', $function['body']['deployment']); $this->assertEquals([ 'users.*.update.name', 'users.*.update.email', - ], $response1['body']['events']); - $this->assertEquals('0 0 1 1 *', $response1['body']['schedule']); - $this->assertEquals(15, $response1['body']['timeout']); + ], $function['body']['events']); + $this->assertEquals('0 0 1 1 *', $function['body']['schedule']); + $this->assertEquals(15, $function['body']['timeout']); - /** - * Create global variable to test in execution later - */ - $headers = [ - 'content-type' => 'application/json', - 'origin' => 'http://localhost', - 'cookie' => 'a_session_console=' . $this->getRoot()['session'], - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-mode' => 'admin', - ]; - - $variable = $this->client->call(Client::METHOD_POST, '/project/variables', $headers, [ + // Create a variable for later tests + $variable = $this->createVariable($data['functionId'], [ 'key' => 'GLOBAL_VARIABLE', 'value' => 'Global Variable Value', ]); - $this->assertEquals(201, $variable['headers']['status-code']); /** @@ -388,7 +276,7 @@ class FunctionsCustomServerTest extends Scope public function testCreateDeploymentFromCLI() { - $function = $this->createFunction([ + $functionId = $this->setupFunction([ 'functionId' => ID::unique(), 'name' => 'test-cli-deployment', 'execute' => [Role::user($this->getUser()['$id'])->toString()], @@ -401,26 +289,29 @@ class FunctionsCustomServerTest extends Scope 'schedule' => '0 0 1 1 *', // Once a year 'timeout' => 10, ]); - $functionId = $function['body']['$id']; - $this->assertEquals(201, $function['headers']['status-code']); - - $deployment = $this->createDeployment($functionId, [ + $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', array_merge([ + 'content-type' => 'multipart/form-data', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + 'x-sdk-language' => 'cli', + ], $this->getHeaders()), [ 'entrypoint' => 'index.php', 'code' => $this->packageFunction('php'), 'activate' => true - ], cli: true); - $deploymentId = $deployment['body']['$id']; - + ]); + $deploymentId = $deployment['body']['$id'] ?? ''; $this->assertEquals(202, $deployment['headers']['status-code']); - $this->assertDeploymentStatus($functionId, $deploymentId, 'ready'); - $deployment = $this->getDeployment($functionId, $deploymentId); - $this->assertEquals(200, $deployment['headers']['status-code']); - $this->assertEquals('cli', $deployment['body']['type']); + $this->assertEventually(function () use ($functionId, $deploymentId) { + $deployment = $this->getDeployment($functionId, $deploymentId); + $this->assertEquals(200, $deployment['headers']['status-code']); + $this->assertEquals('completed', $deployment['body']['status']); + $this->assertEquals('cli', $deployment['body']['type']); + }, 500000, 1000); } - public function testCreateDeploymentFromTemplate() + public function testCreateFunctionAndDeploymentFromTemplate() { $starterTemplate = $this->getTemplate('starter'); @@ -451,7 +342,7 @@ class FunctionsCustomServerTest extends Scope 'templateVersion' => $starterTemplate['body']['providerVersion'], ] ); - $functionId = $function['body']['$id']; + $functionId = $functionId = $function['body']['$id'] ?? ''; $this->assertEquals(201, $function['headers']['status-code']); $this->assertNotEmpty($function['body']['$id']); @@ -464,10 +355,12 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals(0, $lastDeployment['size']); $deploymentId = $lastDeployment['$id']; - $this->assertDeploymentStatus($function['body']['$id'], $deploymentId, 'ready'); - - $deployment = $this->getDeployment($function['body']['$id'], $deploymentId); - $this->assertGreaterThan(0, $deployment['body']['size']); + $this->assertEventually(function () use ($functionId, $deploymentId) { + $deployment = $this->getDeployment($functionId, $deploymentId); + $this->assertEquals(200, $deployment['headers']['status-code']); + $this->assertEquals('completed', $deployment['body']['status']); + $this->assertEquals('cli', $deployment['body']['type']); + }, 500000, 1000); $function = $this->getFunction($functionId); $this->assertEquals(200, $function['headers']['status-code']); @@ -500,18 +393,16 @@ class FunctionsCustomServerTest extends Scope 'path' => '/ping', 'async' => true ]); - $executionId = $execution['body']['$id']; + $executionId = $execution['body']['$id'] ?? ''; $this->assertEquals(202, $execution['headers']['status-code']); $this->assertNotEmpty($execution['body']['$id']); $this->assertEquals('waiting', $execution['body']['status']); $this->assertEventually(function () use ($functionId, $executionId, $totalUsers) { $execution = $this->getExecution($functionId, $executionId); - - $this->assertEquals('completed', $execution['body']['status']); $this->assertEquals(200, $execution['headers']['status-code']); - $this->assertEquals("completed", $execution['body']['status']); $this->assertEquals(200, $execution['body']['responseStatusCode']); + $this->assertEquals('completed', $execution['body']['status']); $this->assertEmpty($execution['body']['responseBody']); $this->assertEmpty($execution['body']['errors']); $this->assertStringContainsString("Total users: " . $totalUsers, $execution['body']['logs']); @@ -522,21 +413,18 @@ class FunctionsCustomServerTest extends Scope } /** - * @depends testUpdate + * @depends testUpdateFunction */ public function testCreateDeployment($data): array { /** * Test for SUCCESS */ - $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $data['functionId'] . '/deployments', array_merge([ - 'content-type' => 'multipart/form-data', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + $functionId = $data['functionId']; + $deployment = $this->createDeployment($functionId, [ 'code' => $this->packageFunction('php'), 'activate' => true ]); - $deploymentId = $deployment['body']['$id'] ?? ''; $this->assertEquals(202, $deployment['headers']['status-code']); @@ -544,37 +432,31 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals(true, (new DatetimeValidator())->isValid($deployment['body']['$createdAt'])); $this->assertEquals('index.php', $deployment['body']['entrypoint']); - $this->assertDeployment($data['functionId'], $deploymentId); - - $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $data['functionId'] . '/deployments', array_merge([ - 'content-type' => 'multipart/form-data', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + $deployment = $this->createDeployment($functionId, [ 'code' => $this->packageFunction('php'), - 'activate' => 'false' + 'activate' => false ]); - + $deploymentIdInactive = $deployment['body']['$id']; $this->assertEquals(202, $deployment['headers']['status-code']); $this->assertNotEmpty($deployment['body']['$id']); - $deploymentIdInactive = $deployment['body']['$id']; + $this->assertEventually(function () use ($functionId, $deploymentId, $deploymentIdInactive) { + $deployment = $this->getDeployment($functionId, $deploymentId); + $this->assertEquals('completed', $deployment['body']['status']); - $this->assertDeployment($data['functionId'], $deploymentIdInactive); - - $function = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'], array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); + $deployment = $this->getDeployment($functionId, $deploymentIdInactive); + $this->assertEquals('completed', $deployment['body']['status']); + }, 500000, 1000); + $function = $this->getFunction($functionId); $this->assertEquals(200, $function['headers']['status-code']); $this->assertEquals($deploymentId, $function['body']['deployment']); $this->assertNotEquals($deploymentIdInactive, $function['body']['deployment']); - $deployment = $this->client->call(Client::METHOD_DELETE, '/functions/' . $data['functionId'] . '/deployments/' . $deploymentIdInactive, array_merge([ + $deployment = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId . '/deployments/' . $deploymentIdInactive, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), []); - $this->assertEquals(204, $deployment['headers']['status-code']); return array_merge($data, ['deploymentId' => $deploymentId]); @@ -585,14 +467,11 @@ class FunctionsCustomServerTest extends Scope */ public function testCancelDeploymentBuild($data): void { - $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $data['functionId'] . '/deployments', array_merge([ - 'content-type' => 'multipart/form-data', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + $functionId = $data['functionId']; + $deployment = $this->createDeployment($functionId, [ 'code' => $this->packageFunction('php'), - 'activate' => true + 'activate' => false ]); - $deploymentId = $deployment['body']['$id'] ?? ''; $this->assertEquals(202, $deployment['headers']['status-code']); @@ -600,39 +479,28 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals(true, (new DatetimeValidator())->isValid($deployment['body']['$createdAt'])); $this->assertEquals('index.php', $deployment['body']['entrypoint']); - $this->assertEventually(function () use ($data, $deploymentId) { - $deployment = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/deployments/' . $deploymentId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ]); - + $this->assertEventually(function () use ($functionId, $deploymentId) { + $deployment = $this->getDeployment($functionId, $deploymentId); $this->assertEquals(200, $deployment['headers']['status-code']); $this->assertEquals('building', $deployment['body']['status']); - }, 60000, 1000); + }, 100000, 250); - // Cancel the deployment build - $cancel = $this->client->call(Client::METHOD_PATCH, '/functions/' . $data['functionId'] . '/deployments/' . $deploymentId . '/build', [ + // Cancel the deployment + $cancel = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId . '/build', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ]); - + ], $this->getHeaders())); $this->assertEquals(200, $cancel['headers']['status-code']); $this->assertEquals('canceled', $cancel['body']['status']); /** * Build worker still runs the build. - * 10s sleep gives worker enough time to finish build. + * 30s sleep gives worker enough time to finish build. * After build finished, it should still be canceled, not ready. */ - \sleep(10); - $deployment = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/deployments/' . $deploymentId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ]); + \sleep(30); + $deployment = $this->getDeployment($functionId, $deploymentId); $this->assertEquals(200, $deployment['headers']['status-code']); $this->assertEquals('canceled', $deployment['body']['status']); } @@ -645,10 +513,11 @@ class FunctionsCustomServerTest extends Scope /** * Test for Large Code File SUCCESS */ + $functionId = $data['functionId']; $folder = 'php-large'; $code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz"; - $this->packageCode($folder); + Console::execute('cd ' . realpath(__DIR__ . "/../../../resources/functions") . "/$folder && tar --exclude code.tar.gz -czf code.tar.gz .", '', $this->stdout, $this->stderr); $chunkSize = 5 * 1024 * 1024; $handle = @fopen($code, "rb"); @@ -666,7 +535,7 @@ class FunctionsCustomServerTest extends Scope if (!empty($id)) { $headers['x-appwrite-id'] = $id; } - $largeTag = $this->client->call(Client::METHOD_POST, '/functions/' . $data['functionId'] . '/deployments', array_merge($headers, $this->getHeaders()), [ + $largeTag = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', array_merge($headers, $this->getHeaders()), [ 'entrypoint' => 'index.php', 'code' => $curlFile, 'activate' => true, @@ -685,19 +554,15 @@ class FunctionsCustomServerTest extends Scope $this->assertLessThan(1024 * 1024 * 10, $largeTag['body']['size']); // ~7MB video file $deploymentSize = $largeTag['body']['size']; - $deploymentId = $largeTag['body']['$id']; - $this->assertDeployment($data['functionId'], $deploymentId, true); - - $response = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/deployments/' . $deploymentId, array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), []); - - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals($deploymentSize, $response['body']['size']); - $this->assertGreaterThan(1024 * 1024 * 10, $response['body']['buildSize']); // ~7MB video file + 10MB sample file + $this->assertEventually(function () use ($functionId, $deploymentId, $deploymentSize) { + $deployment = $this->getDeployment($functionId, $deploymentId); + $this->assertEquals(200, $deployment['headers']['status-code']); + $this->assertEquals('completed', $deployment['body']['status']); + $this->assertEquals($deploymentSize, $deployment['body']['size']); + $this->assertGreaterThan(1024 * 1024 * 10, $deployment['body']['buildSize']); // ~7MB video file + 10MB sample file + }, 500000, 1000); return $data; } @@ -710,6 +575,8 @@ class FunctionsCustomServerTest extends Scope /** * Test for SUCCESS */ + $dateValidator = new DatetimeValidator(); + $response = $this->client->call(Client::METHOD_PATCH, '/functions/' . $data['functionId'] . '/deployments/' . $data['deploymentId'], array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -717,15 +584,10 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals(200, $response['headers']['status-code']); $this->assertNotEmpty($response['body']['$id']); - $dateValidator = new DatetimeValidator(); $this->assertEquals(true, $dateValidator->isValid($response['body']['$createdAt'])); $this->assertEquals(true, $dateValidator->isValid($response['body']['$updatedAt'])); $this->assertEquals($data['deploymentId'], $response['body']['deployment']); - /** - * Test for FAILURE - */ - return $data; } @@ -737,127 +599,85 @@ class FunctionsCustomServerTest extends Scope /** * Test for SUCCESS */ - $function = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/deployments', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); + $functionId = $data['functionId']; + $deployments = $this->listDeployments($functionId); - $this->assertEquals($function['headers']['status-code'], 200); - $this->assertEquals($function['body']['total'], 3); - $this->assertIsArray($function['body']['deployments']); - $this->assertCount(3, $function['body']['deployments']); - $this->assertArrayHasKey('size', $function['body']['deployments'][0]); - $this->assertArrayHasKey('buildSize', $function['body']['deployments'][0]); + $this->assertEquals($deployments['headers']['status-code'], 200); + $this->assertEquals($deployments['body']['total'], 3); + $this->assertIsArray($deployments['body']['deployments']); + $this->assertCount(3, $deployments['body']['deployments']); + $this->assertArrayHasKey('size', $deployments['body']['deployments'][0]); + $this->assertArrayHasKey('buildSize', $deployments['body']['deployments'][0]); - /** - * Test search queries - */ - $function = $this->client->call( - Client::METHOD_GET, - '/functions/' . $data['functionId'] . '/deployments', - array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders(), [ - 'search' => $data['functionId'] - ]) - ); + // Test search id + $deployments = $this->listDeployments($functionId, [ + 'search' => $data['functionId'] + ]); + $this->assertEquals($deployments['headers']['status-code'], 200); + $this->assertEquals(3, $deployments['body']['total']); + $this->assertIsArray($deployments['body']['deployments']); + $this->assertCount(3, $deployments['body']['deployments']); + $this->assertEquals($deployments['body']['deployments'][0]['$id'], $data['deploymentId']); - $this->assertEquals($function['headers']['status-code'], 200); - $this->assertEquals(3, $function['body']['total']); - $this->assertIsArray($function['body']['deployments']); - $this->assertCount(3, $function['body']['deployments']); - $this->assertEquals($function['body']['deployments'][0]['$id'], $data['deploymentId']); - - $function = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/deployments', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + // Test pagination limit + $deployments = $this->listDeployments($functionId, [ 'queries' => [ Query::limit(1)->toString(), ], ]); + $this->assertEquals($deployments['headers']['status-code'], 200); + $this->assertCount(1, $deployments['body']['deployments']); - $this->assertEquals($function['headers']['status-code'], 200); - $this->assertCount(1, $function['body']['deployments']); - - $function = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/deployments', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + $deployments = $this->listDeployments($functionId, [ 'queries' => [ Query::offset(1)->toString(), ], ]); - $this->assertEquals($function['headers']['status-code'], 200); - $this->assertCount(2, $function['body']['deployments']); + $this->assertEquals($deployments['headers']['status-code'], 200); + $this->assertCount(2, $deployments['body']['deployments']); - $function = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/deployments', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + $deployments = $this->listDeployments($functionId, [ 'queries' => [ Query::equal('entrypoint', ['index.php'])->toString(), ], ]); - $this->assertEquals($function['headers']['status-code'], 200); - $this->assertCount(3, $function['body']['deployments']); + $this->assertEquals($deployments['headers']['status-code'], 200); + $this->assertCount(3, $deployments['body']['deployments']); - $function = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/deployments', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + $deployments = $this->listDeployments($functionId, [ 'queries' => [ Query::equal('entrypoint', ['index.js'])->toString(), ], ]); - $this->assertEquals($function['headers']['status-code'], 200); - $this->assertCount(0, $function['body']['deployments']); + $this->assertEquals($deployments['headers']['status-code'], 200); + $this->assertCount(0, $deployments['body']['deployments']); - $function = $this->client->call( - Client::METHOD_GET, - '/functions/' . $data['functionId'] . '/deployments', - array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders(), [ - 'search' => 'Test' - ]) - ); + $deployments = $this->listDeployments($functionId, [ + 'search' => 'Test' + ]); - $this->assertEquals($function['headers']['status-code'], 200); - $this->assertEquals(3, $function['body']['total']); - $this->assertIsArray($function['body']['deployments']); - $this->assertCount(3, $function['body']['deployments']); - $this->assertEquals($function['body']['deployments'][0]['$id'], $data['deploymentId']); - $function = $this->client->call( - Client::METHOD_GET, - '/functions/' . $data['functionId'] . '/deployments', - array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders(), [ - 'search' => 'php-8.0' - ]) - ); + $this->assertEquals($deployments['headers']['status-code'], 200); + $this->assertEquals(3, $deployments['body']['total']); + $this->assertIsArray($deployments['body']['deployments']); + $this->assertCount(3, $deployments['body']['deployments']); + $this->assertEquals($deployments['body']['deployments'][0]['$id'], $data['deploymentId']); - $this->assertEquals($function['headers']['status-code'], 200); - $this->assertEquals(3, $function['body']['total']); - $this->assertIsArray($function['body']['deployments']); - $this->assertCount(3, $function['body']['deployments']); - $this->assertEquals($function['body']['deployments'][0]['$id'], $data['deploymentId']); + $deployments = $this->listDeployments($functionId, [ + 'search' => 'php-8.0' + ]); - $function = $this->client->call( - Client::METHOD_GET, - '/functions/' . $data['functionId'] . '/deployments', - array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), + $this->assertEquals($deployments['headers']['status-code'], 200); + $this->assertEquals(3, $deployments['body']['total']); + $this->assertIsArray($deployments['body']['deployments']); + $this->assertCount(3, $deployments['body']['deployments']); + $this->assertEquals($deployments['body']['deployments'][0]['$id'], $data['deploymentId']); + + $deployments = $this->listDeployments( + $functionId, [ 'queries' => [ Query::equal('type', ['manual'])->toString(), @@ -865,16 +685,11 @@ class FunctionsCustomServerTest extends Scope ] ); - $this->assertEquals($function['headers']['status-code'], 200); - $this->assertEquals(3, $function['body']['total']); + $this->assertEquals($deployments['headers']['status-code'], 200); + $this->assertEquals(3, $deployments['body']['total']); - $function = $this->client->call( - Client::METHOD_GET, - '/functions/' . $data['functionId'] . '/deployments', - array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), + $deployments = $this->listDeployments( + $functionId, [ 'queries' => [ Query::equal('type', ['vcs'])->toString(), @@ -882,16 +697,11 @@ class FunctionsCustomServerTest extends Scope ] ); - $this->assertEquals($function['headers']['status-code'], 200); - $this->assertEquals(0, $function['body']['total']); + $this->assertEquals($deployments['headers']['status-code'], 200); + $this->assertEquals(0, $deployments['body']['total']); - $function = $this->client->call( - Client::METHOD_GET, - '/functions/' . $data['functionId'] . '/deployments', - array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), + $deployments = $this->listDeployments( + $functionId, [ 'queries' => [ Query::equal('type', ['invalid-string'])->toString(), @@ -899,16 +709,11 @@ class FunctionsCustomServerTest extends Scope ] ); - $this->assertEquals($function['headers']['status-code'], 200); - $this->assertEquals(0, $function['body']['total']); + $this->assertEquals($deployments['headers']['status-code'], 200); + $this->assertEquals(0, $deployments['body']['total']); - $function = $this->client->call( - Client::METHOD_GET, - '/functions/' . $data['functionId'] . '/deployments', - array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), + $deployments = $this->listDeployments( + $functionId, [ 'queries' => [ Query::greaterThan('size', 10000)->toString(), @@ -916,16 +721,11 @@ class FunctionsCustomServerTest extends Scope ] ); - $this->assertEquals($function['headers']['status-code'], 200); - $this->assertEquals(1, $function['body']['total']); + $this->assertEquals($deployments['headers']['status-code'], 200); + $this->assertEquals(1, $deployments['body']['total']); - $function = $this->client->call( - Client::METHOD_GET, - '/functions/' . $data['functionId'] . '/deployments', - array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), + $deployments = $this->listDeployments( + $functionId, [ 'queries' => [ Query::greaterThan('size', 0)->toString(), @@ -933,57 +733,40 @@ class FunctionsCustomServerTest extends Scope ] ); - $this->assertEquals($function['headers']['status-code'], 200); - $this->assertEquals(3, $function['body']['total']); + $this->assertEquals($deployments['headers']['status-code'], 200); + $this->assertEquals(3, $deployments['body']['total']); - $function = $this->client->call( - Client::METHOD_GET, - '/functions/' . $data['functionId'] . '/deployments', - array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), + $deployments = $this->listDeployments( + $functionId, [ 'queries' => [ Query::greaterThan('size', -100)->toString(), ], ] ); - - $this->assertEquals($function['headers']['status-code'], 200); - $this->assertEquals(3, $function['body']['total']); + $this->assertEquals($deployments['headers']['status-code'], 200); + $this->assertEquals(3, $deployments['body']['total']); /** * Ensure size output and size filters work exactly. * Prevents buildSize being counted towards deployemtn size */ - $response = $this->client->call( - Client::METHOD_GET, - '/functions/' . $data['functionId'] . '/deployments', - array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), + $deployments = $this->listDeployments( + $functionId, [ Query::limit(1)->toString(), ] ); + $this->assertEquals(200, $deployments['headers']['status-code']); + $this->assertGreaterThanOrEqual(1, $deployments['body']['total']); + $this->assertNotEmpty($deployments['body']['deployments'][0]['$id']); + $this->assertNotEmpty($deployments['body']['deployments'][0]['size']); - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertGreaterThanOrEqual(1, $response['body']['total']); - $this->assertNotEmpty($response['body']['deployments'][0]['$id']); - $this->assertNotEmpty($response['body']['deployments'][0]['size']); + $deploymentId = $deployments['body']['deployments'][0]['$id']; + $deploymentSize = $deployments['body']['deployments'][0]['size']; - $deploymentId = $function['body']['deployments'][0]['$id']; - $deploymentSize = $function['body']['deployments'][0]['size']; - - $response = $this->client->call( - Client::METHOD_GET, - '/functions/' . $data['functionId'] . '/deployments', - array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), + $deployments = $this->listDeployments( + $functionId, [ 'queries' => [ Query::equal('size', [$deploymentSize])->toString(), @@ -991,20 +774,21 @@ class FunctionsCustomServerTest extends Scope ] ); - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertGreaterThan(0, $response['body']['total']); + $this->assertEquals(200, $deployments['headers']['status-code']); + $this->assertGreaterThan(0, $deployments['body']['total']); - $found = false; - foreach ($response['body']['deployments'] as $deployment) { - if ($deployment['$id'] === $deploymentId) { - $found = true; - $this->assertEquals($deploymentSize, $deployment['size']); - break; - } + $matchingDeployment = array_filter( + $deployments['body']['deployments'], + fn($deployment) => $deployment['$id'] === $deploymentId + ); + + $this->assertNotEmpty($matchingDeployment, "Deployment with ID {$deploymentId} not found"); + + if (!empty($matchingDeployment)) { + $deployment = reset($matchingDeployment); + $this->assertEquals($deploymentSize, $deployment['size']); } - $this->assertTrue($found); - return $data; } @@ -1016,27 +800,19 @@ class FunctionsCustomServerTest extends Scope /** * Test for SUCCESS */ - $function = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/deployments/' . $data['deploymentId'], array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $this->assertEquals(200, $function['headers']['status-code']); - $this->assertGreaterThan(0, $function['body']['buildTime']); - $this->assertNotEmpty($function['body']['status']); - $this->assertNotEmpty($function['body']['buildLogs']); - $this->assertArrayHasKey('size', $function['body']); - $this->assertArrayHasKey('buildSize', $function['body']); + $deployment = $this->getDeployment($data['functionId'], $data['deploymentId']); + $this->assertEquals(200, $deployment['headers']['status-code']); + $this->assertGreaterThan(0, $deployment['body']['buildTime']); + $this->assertNotEmpty($deployment['body']['status']); + $this->assertNotEmpty($deployment['body']['buildLogs']); + $this->assertArrayHasKey('size', $deployment['body']); + $this->assertArrayHasKey('buildSize', $deployment['body']); /** * Test for FAILURE */ - $function = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/deployments/x', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $this->assertEquals($function['headers']['status-code'], 404); + $deployment = $this->getDeployment($data['functionId'], 'x'); + $this->assertEquals($deployment['headers']['status-code'], 404); return $data; } @@ -1049,13 +825,9 @@ class FunctionsCustomServerTest extends Scope /** * Test for SUCCESS */ - $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $data['functionId'] . '/executions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + $execution = $this->createExecution($data['functionId'], [ 'async' => false, ]); - $executionId = $execution['body']['$id'] ?? ''; $this->assertEquals(201, $execution['headers']['status-code']); @@ -1077,10 +849,7 @@ class FunctionsCustomServerTest extends Scope $this->assertNotEmpty($execution['body']['logs']); $this->assertLessThan(10, $execution['body']['duration']); - $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $data['functionId'] . '/executions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + $execution = $this->createExecution($data['functionId'], [ 'async' => false, 'path' => '/?code=400' ]); @@ -1092,6 +861,8 @@ class FunctionsCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), []); + + $execution = $this->createExecution($data['functionId']); $this->assertEquals(204, $execution['headers']['status-code']); return array_merge($data, ['executionId' => $executionId]); @@ -1105,82 +876,57 @@ class FunctionsCustomServerTest extends Scope /** * Test for SUCCESS */ - $function = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/executions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); + $executions = $this->listExecutions($data['functionId']); + $this->assertEquals($executions['headers']['status-code'], 200); + $this->assertEquals($executions['body']['total'], 1); + $this->assertIsArray($executions['body']['executions']); + $this->assertCount(1, $executions['body']['executions']); + $this->assertEquals($executions['body']['executions'][0]['$id'], $data['executionId']); - $this->assertEquals($function['headers']['status-code'], 200); - $this->assertEquals($function['body']['total'], 1); - $this->assertIsArray($function['body']['executions']); - $this->assertCount(1, $function['body']['executions']); - $this->assertEquals($function['body']['executions'][0]['$id'], $data['executionId']); - - $response = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/executions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + $executions = $this->listExecutions($data['functionId'], [ 'queries' => [ Query::limit(1)->toString(), ], ]); + $this->assertEquals(200, $executions['headers']['status-code']); + $this->assertCount(1, $executions['body']['executions']); - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertCount(1, $response['body']['executions']); - - $response = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/executions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + $executions = $this->listExecutions($data['functionId'], [ 'queries' => [ Query::offset(1)->toString(), ], ]); + $this->assertEquals(200, $executions['headers']['status-code']); + $this->assertCount(0, $executions['body']['executions']); - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertCount(0, $response['body']['executions']); - - $response = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/executions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + $executions = $this->listExecutions($data['functionId'], [ 'queries' => [ Query::equal('trigger', ['http'])->toString(), ], ]); - - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertCount(1, $response['body']['executions']); + $this->assertEquals(200, $executions['headers']['status-code']); + $this->assertCount(1, $executions['body']['executions']); /** * Test search queries */ - - $response = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/executions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + $executions = $this->listExecutions($data['functionId'], [ 'search' => $data['executionId'], ]); + $this->assertEquals(200, $executions['headers']['status-code']); + $this->assertEquals(1, $executions['body']['total']); + $this->assertIsInt($executions['body']['total']); + $this->assertCount(1, $executions['body']['executions']); + $this->assertEquals($data['functionId'], $executions['body']['executions'][0]['functionId']); - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals(1, $response['body']['total']); - $this->assertIsInt($response['body']['total']); - $this->assertCount(1, $response['body']['executions']); - $this->assertEquals($data['functionId'], $response['body']['executions'][0]['functionId']); - - $response = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/executions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + $executions = $this->listExecutions($data['functionId'], [ 'search' => $data['functionId'], ]); - - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals(1, $response['body']['total']); - $this->assertIsInt($response['body']['total']); - $this->assertCount(1, $response['body']['executions']); - $this->assertEquals($data['executionId'], $response['body']['executions'][0]['$id']); + $this->assertEquals(200, $executions['headers']['status-code']); + $this->assertEquals(1, $executions['body']['total']); + $this->assertIsInt($executions['body']['total']); + $this->assertCount(1, $executions['body']['executions']); + $this->assertEquals($data['executionId'], $executions['body']['executions'][0]['$id']); return $data; } @@ -1193,13 +939,9 @@ class FunctionsCustomServerTest extends Scope /** * Test for SUCCESS */ - $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $data['functionId'] . '/executions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + $execution = $this->createExecution($data['functionId'], [ // Testing default value, should be 'async' => false ]); - $this->assertEquals(201, $execution['headers']['status-code']); $this->assertEquals('completed', $execution['body']['status']); $this->assertEquals(200, $execution['body']['responseStatusCode']); @@ -1221,22 +963,14 @@ class FunctionsCustomServerTest extends Scope /** * Test for SUCCESS */ - $function = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/executions/' . $data['executionId'], array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $this->assertEquals($function['headers']['status-code'], 200); - $this->assertEquals($function['body']['$id'], $data['executionId']); + $execution = $this->getExecution($data['functionId'], $data['executionId']); + $this->assertEquals($execution['headers']['status-code'], 200); + $this->assertEquals($execution['body']['$id'], $data['executionId']); /** * Test for FAILURE */ - $function = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/executions/x', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - + $function = $this->getExecution($data['functionId'], 'x'); $this->assertEquals($function['headers']['status-code'], 404); return $data; @@ -1329,7 +1063,8 @@ class FunctionsCustomServerTest extends Scope /** * Test for SUCCESS */ - $response1 = $this->client->call(Client::METHOD_PUT, '/functions/' . $data['functionId'], array_merge([ + // Change the function specs + $function = $this->client->call(Client::METHOD_PUT, '/functions/' . $data['functionId'], array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -1344,22 +1079,19 @@ class FunctionsCustomServerTest extends Scope 'specification' => Specification::S_1VCPU_1GB, ]); - $this->assertEquals(200, $response1['headers']['status-code']); - $this->assertNotEmpty($response1['body']['$id']); - $this->assertEquals(Specification::S_1VCPU_1GB, $response1['body']['specification']); + $this->assertEquals(200, $function['headers']['status-code']); + $this->assertNotEmpty($function['body']['$id']); + $this->assertEquals(Specification::S_1VCPU_1GB, $function['body']['specification']); - // Test Execution - $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $data['functionId'] . '/executions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); + // Verify the updated specs + $execution = $this->createExecution($data['functionId']); $output = json_decode($execution['body']['responseBody'], true); - $this->assertEquals(1, $output['APPWRITE_FUNCTION_CPUS']); $this->assertEquals(1024, $output['APPWRITE_FUNCTION_MEMORY']); - $response2 = $this->client->call(Client::METHOD_PUT, '/functions/' . $data['functionId'], array_merge([ + // Change the specs to 1vcpu 512mb + $function = $this->client->call(Client::METHOD_PUT, '/functions/' . $data['functionId'], array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -1374,25 +1106,21 @@ class FunctionsCustomServerTest extends Scope 'specification' => Specification::S_1VCPU_512MB, ]); - $this->assertEquals(200, $response2['headers']['status-code']); - $this->assertNotEmpty($response2['body']['$id']); - $this->assertEquals(Specification::S_1VCPU_512MB, $response2['body']['specification']); + $this->assertEquals(200, $function['headers']['status-code']); + $this->assertNotEmpty($function['body']['$id']); + $this->assertEquals(Specification::S_1VCPU_512MB, $function['body']['specification']); - // Test Execution - $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $data['functionId'] . '/executions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); + // Verify the updated specs + $execution = $this->createExecution($data['functionId']); $output = json_decode($execution['body']['responseBody'], true); - $this->assertEquals(1, $output['APPWRITE_FUNCTION_CPUS']); $this->assertEquals(512, $output['APPWRITE_FUNCTION_MEMORY']); /** * Test for FAILURE */ - $response3 = $this->client->call(Client::METHOD_PUT, '/functions/' . $data['functionId'], array_merge([ + $function = $this->client->call(Client::METHOD_PUT, '/functions/' . $data['functionId'], array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -1406,9 +1134,8 @@ class FunctionsCustomServerTest extends Scope 'entrypoint' => 'index.php', 'specification' => 's-2vcpu-512mb', // Invalid specification ]); - - $this->assertEquals(400, $response3['headers']['status-code']); - $this->assertStringStartsWith('Invalid `specification` param: Specification must be one of:', $response3['body']['message']); + $this->assertEquals(400, $function['headers']['status-code']); + $this->assertStringStartsWith('Invalid `specification` param: Specification must be one of:', $function['body']['message']); return $data; } @@ -1421,24 +1148,15 @@ class FunctionsCustomServerTest extends Scope /** * Test for SUCCESS */ - $function = $this->client->call(Client::METHOD_DELETE, '/functions/' . $data['functionId'] . '/deployments/' . $data['deploymentId'], array_merge([ + $deployment = $this->client->call(Client::METHOD_DELETE, '/functions/' . $data['functionId'] . '/deployments/' . $data['deploymentId'], array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); + $this->assertEquals(204, $deployment['headers']['status-code']); + $this->assertEmpty($deployment['body']); - $this->assertEquals(204, $function['headers']['status-code']); - $this->assertEmpty($function['body']); - - $function = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/deployments/' . $data['deploymentId'], array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $this->assertEquals(404, $function['headers']['status-code']); - - /** - * Test for FAILURE - */ + $deployment = $this->getDeployment($data['functionId'], $data['deploymentId']); + $this->assertEquals(404, $deployment['headers']['status-code']); return $data; } @@ -1446,87 +1164,60 @@ class FunctionsCustomServerTest extends Scope /** * @depends testCreateDeployment */ - public function testDelete($data): array + public function testDeleteFunction($data): array { /** * Test for SUCCESS */ - $function = $this->client->call(Client::METHOD_DELETE, '/functions/' . $data['functionId'], array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - + $function = $this->deleteFunction($data['functionId']); $this->assertEquals(204, $function['headers']['status-code']); $this->assertEmpty($function['body']); - $function = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'], array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - + $function = $this->getFunction($data['functionId']); $this->assertEquals(404, $function['headers']['status-code']); - /** - * Test for FAILURE - */ - return $data; } - public function testTimeout() + public function testExecutionTimeout() { - $function = $this->createFunction([ + $functionId = $this->setupFunction([ 'name' => 'timeout-php-8.0', 'runtime' => 'php-8.0', 'entrypoint' => 'index.php', 'events' => [], - 'schedule' => '*/10 * * * *', // Execute every 10 seconds - 'timeout' => 15, + 'schedule' => '', + 'timeout' => 5, // Should timeout after 5 seconds + ]); + $this->setupDeployment($functionId, [ + 'code' => $this->packageFunction('php'), + 'activate' => true, ]); - $functionId = $function['body']['id']; - $this->assertEquals(201, $function['headers']['status-code']); - $deployment = $this->createDeployment($functionId, 'php'); - $deploymentId = $deployment['body']['$id']; - $this->assertDeploymentStatus($functionId, $deploymentId, 'ready'); - - $execution = $this->createExecution($functionId, ['async' => true]); + $execution = $this->createExecution($functionId, [ + 'async' => true + ]); $executionId = $execution['body']['$id'] ?? ''; $this->assertEquals(202, $execution['headers']['status-code']); + \sleep(5); // Wait for the function to timeout + $this->assertEventually(function () use ($functionId, $executionId) { $execution = $this->getExecution($functionId, $executionId); $this->assertEquals(200, $execution['headers']['status-code']); $this->assertEquals('failed', $execution['body']['status']); - $this->assertEquals('failed', $execution['body']['status']); $this->assertEquals(500, $execution['body']['responseStatusCode']); $this->assertGreaterThan(2, $execution['body']['duration']); $this->assertLessThan(20, $execution['body']['duration']); $this->assertEquals('', $execution['body']['responseBody']); $this->assertEquals('', $execution['body']['logs']); $this->assertStringContainsString('timed out', $execution['body']['errors']); - }, 20000, 500); + }, 5000, 250); - $this->assertEventually(function () use ($functionId) { - $executions = $this->listExecutions($functionId, [ - 'queries' => [ - Query::equal('trigger', ['schedule'])->toString(), - ], - ]); - - $this->assertEquals(200, $executions['headers']['status-code']); - $this->assertEquals(1, $executions['body']['total']); - - $lastExecution = $executions['body']['executions'][0]; - $this->assertEquals('schedule', $lastExecution['trigger']); - }, 20000, 500); - - $function = $this->deleteFunction($functionId); - $this->assertEquals(204, $function['headers']['status-code']); + $this->cleanupFunction($functionId); } - /** * * @return array @@ -1554,61 +1245,33 @@ class FunctionsCustomServerTest extends Scope */ public function testCreateCustomExecution(string $folder, string $name, string $entrypoint, string $runtimeName, string $runtimeVersion) { - $timeout = 15; - $code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz"; - $this->packageCode($folder); - - $function = $this->client->call(Client::METHOD_POST, '/functions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + $functionId = $this->setupFunction([ 'functionId' => ID::unique(), 'name' => 'Test ' . $name, 'runtime' => $name, 'entrypoint' => $entrypoint, 'events' => [], - 'timeout' => $timeout, + 'timeout' => 15, ]); - $functionId = $function['body']['$id'] ?? ''; - - $this->assertEquals(201, $function['headers']['status-code']); - - $variable = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/variables', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + $variable = $this->createVariable($functionId, [ 'key' => 'CUSTOM_VARIABLE', - 'value' => 'variable', + 'value' => 'variable' ]); - $this->assertEquals(201, $variable['headers']['status-code']); - $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', array_merge([ - 'content-type' => 'multipart/form-data', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + $deploymentId = $this->setupDeployment($functionId, [ 'entrypoint' => $entrypoint, - 'code' => new CURLFile($code, 'application/x-gzip', basename($code)), + 'code' => $this->packageFunction($folder), 'activate' => true ]); - $deploymentId = $deployment['body']['$id'] ?? ''; - $this->assertEquals(202, $deployment['headers']['status-code']); - - $this->assertDeployment($function['body']['$id'], $deploymentId); - - $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + $execution = $this->createExecution($functionId, [ 'body' => 'foobar', 'async' => false ]); - $executionId = $execution['body']['$id'] ?? ''; $output = json_decode($execution['body']['responseBody'], true); - $this->assertEquals(201, $execution['headers']['status-code']); $this->assertEquals('completed', $execution['body']['status']); $this->assertEquals(200, $execution['body']['responseStatusCode']); @@ -1627,11 +1290,7 @@ class FunctionsCustomServerTest extends Scope $this->assertStringContainsString('Amazing Function Log', $execution['body']['logs']); $this->assertEmpty($execution['body']['errors']); - $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - + $executions = $this->listExecutions($functionId); $this->assertEquals($executions['headers']['status-code'], 200); $this->assertEquals($executions['body']['total'], 1); $this->assertIsArray($executions['body']['executions']); @@ -1640,55 +1299,28 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals($executions['body']['executions'][0]['trigger'], 'http'); $this->assertStringContainsString('Amazing Function Log', $executions['body']['executions'][0]['logs']); - // Cleanup : Delete function - $response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], []); - - $this->assertEquals(204, $response['headers']['status-code']); + $this->cleanupFunction($functionId); } public function testCreateCustomExecutionBinaryResponse() { - $timeout = 15; - $code = realpath(__DIR__ . '/../../../resources/functions') . "/php-binary-response/code.tar.gz"; - $this->packageCode('php-binary-response'); - - $function = $this->client->call(Client::METHOD_POST, '/functions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + $functionId = $this->setupFunction([ 'functionId' => ID::unique(), 'name' => 'Test PHP Binary executions', 'runtime' => 'php-8.0', 'entrypoint' => 'index.php', - 'timeout' => $timeout, + 'timeout' => 15, 'execute' => ['any'] ]); - - $functionId = $function['body']['$id'] ?? ''; - - $this->assertEquals(201, $function['headers']['status-code']); - - $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', array_merge([ - 'content-type' => 'multipart/form-data', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + $this->setupDeployment($functionId, [ 'entrypoint' => 'index.php', - 'code' => new CURLFile($code, 'application/x-gzip', basename($code)), + 'code' => $this->packageFunction('php-binary-response'), 'activate' => true ]); - $deploymentId = $deployment['body']['$id'] ?? ''; - $this->assertEquals(202, $deployment['headers']['status-code']); - - $this->assertDeployment($function['body']['$id'], $deploymentId); - $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([ 'x-appwrite-project' => $this->getProject()['$id'], - 'accept' => 'multipart/form-data', + 'accept' => 'multipart/form-data', // Accept binary response ], $this->getHeaders()), [ 'body' => null, ]); @@ -1708,7 +1340,7 @@ class FunctionsCustomServerTest extends Scope */ $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([ 'x-appwrite-project' => $this->getProject()['$id'], - 'accept' => 'application/json', + 'accept' => 'application/json', // Accept JSON response ], $this->getHeaders()), [ 'body' => null, ]); @@ -1716,56 +1348,29 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals(400, $execution['headers']['status-code']); $this->assertStringContainsString('Failed to parse response', $execution['body']['message']); - // Cleanup : Delete function - $response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], []); - - $this->assertEquals(204, $response['headers']['status-code']); + $this->cleanupFunction($functionId); } public function testCreateCustomExecutionBinaryRequest() { - $timeout = 15; - $code = realpath(__DIR__ . '/../../../resources/functions') . "/php-binary-request/code.tar.gz"; - $this->packageCode('php-binary-request'); - - $function = $this->client->call(Client::METHOD_POST, '/functions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + $functionId = $this->setupFunction([ 'functionId' => ID::unique(), 'name' => 'Test PHP Binary executions', 'runtime' => 'php-8.0', 'entrypoint' => 'index.php', - 'timeout' => $timeout, + 'timeout' => 15, 'execute' => ['any'] ]); - - $functionId = $function['body']['$id'] ?? ''; - - $this->assertEquals(201, $function['headers']['status-code']); - - $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', array_merge([ - 'content-type' => 'multipart/form-data', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + $this->setupDeployment($functionId, [ 'entrypoint' => 'index.php', - 'code' => new CURLFile($code, 'application/x-gzip', basename($code)), + 'code' => $this->packageFunction('php-binary-request'), 'activate' => true ]); - $deploymentId = $deployment['body']['$id'] ?? ''; - $this->assertEquals(202, $deployment['headers']['status-code']); - - $this->assertDeployment($function['body']['$id'], $deploymentId); - $bytes = pack('C*', ...[0, 20, 255]); $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([ - 'content-type' => 'multipart/form-data', + 'content-type' => 'multipart/form-data', // Send binary request 'x-appwrite-project' => $this->getProject()['$id'], 'accept' => 'application/json', ], $this->getHeaders()), [ @@ -1773,7 +1378,6 @@ class FunctionsCustomServerTest extends Scope ], false); $executionBody = json_decode($execution['body'], true); - $this->assertEquals(201, $execution['headers']['status-code']); $this->assertEquals(\md5($bytes), $executionBody['responseBody']); $this->assertStringStartsWith('application/json', $execution['headers']['content-type']); @@ -1782,7 +1386,7 @@ class FunctionsCustomServerTest extends Scope * Test for FAILURE */ $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([ - 'content-type' => 'application/json', + 'content-type' => 'application/json', // Send JSON headers 'x-appwrite-project' => $this->getProject()['$id'], 'accept' => 'application/json', ], $this->getHeaders()), [ @@ -1792,89 +1396,48 @@ class FunctionsCustomServerTest extends Scope $executionBody = json_decode($execution['body'], true); $this->assertNotEquals(\md5($bytes), $executionBody['responseBody']); - // Cleanup : Delete function - $response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], []); - - $this->assertEquals(204, $response['headers']['status-code']); + $this->cleanupFunction($functionId); } public function testv2Function() { - $timeout = 15; - $code = realpath(__DIR__ . '/../../../resources/functions') . "/php-v2/code.tar.gz"; - $this->packageCode('php-v2'); - - $function = $this->client->call(Client::METHOD_POST, '/functions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + $functionId = $this->setupFunction([ 'functionId' => ID::unique(), 'name' => 'Test PHP V2', 'runtime' => 'php-8.0', 'entrypoint' => 'index.php', 'events' => [], - 'timeout' => $timeout, + 'timeout' => 15, ]); - $functionId = $function['body']['$id'] ?? ''; - - $this->assertEquals(201, $function['headers']['status-code']); - - $headers = [ + $variable = $this->client->call(Client::METHOD_PATCH, '/mock/functions-v2', [ 'content-type' => 'application/json', 'origin' => 'http://localhost', 'cookie' => 'a_session_console=' . $this->getRoot()['session'], 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-mode' => 'admin', - ]; - - $variable = $this->client->call(Client::METHOD_PATCH, '/mock/functions-v2', $headers, [ + ], [ 'functionId' => $functionId ]); - $this->assertEquals(204, $variable['headers']['status-code']); - $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', array_merge([ - 'content-type' => 'multipart/form-data', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + $this->setupDeployment($functionId, [ 'entrypoint' => 'index.php', - 'code' => new CURLFile($code, 'application/x-gzip', basename($code)), + 'code' => $this->packageFunction('php-v2'), 'activate' => true ]); - $deploymentId = $deployment['body']['$id'] ?? ''; - $this->assertEquals(202, $deployment['headers']['status-code']); - - $this->assertDeployment($function['body']['$id'], $deploymentId); - - $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + $execution = $this->createExecution($functionId, [ 'body' => 'foobar', 'async' => false ]); - $output = json_decode($execution['body']['responseBody'], true); - $this->assertEquals(201, $execution['headers']['status-code']); $this->assertEquals('completed', $execution['body']['status']); $this->assertEquals(200, $execution['body']['responseStatusCode']); $this->assertEquals(true, $output['v2Woks']); - // Cleanup : Delete function - $response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], []); - - $this->assertEquals(204, $response['headers']['status-code']); + $this->cleanupFunction($functionId); } public function testGetRuntimes() @@ -1902,14 +1465,7 @@ class FunctionsCustomServerTest extends Scope public function testEventTrigger() { - $timeout = 15; - $code = realpath(__DIR__ . '/../../../resources/functions') . "/php-event/code.tar.gz"; - $this->packageCode('php-event'); - - $function = $this->client->call(Client::METHOD_POST, '/functions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + $functionId = $this->setupFunction([ 'functionId' => ID::unique(), 'name' => 'Test PHP Event executions', 'runtime' => 'php-8.0', @@ -1917,28 +1473,15 @@ class FunctionsCustomServerTest extends Scope 'events' => [ 'users.*.create', ], - 'timeout' => $timeout, + 'timeout' => 15, ]); - - $functionId = $function['body']['$id'] ?? ''; - - $this->assertEquals(201, $function['headers']['status-code']); - - $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', array_merge([ - 'content-type' => 'multipart/form-data', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + $this->setupDeployment($functionId, [ 'entrypoint' => 'index.php', - 'code' => new CURLFile($code, 'application/x-gzip', basename($code)), + 'code' => $this->packageFunction('php-event'), 'activate' => true ]); - $deploymentId = $deployment['body']['$id'] ?? ''; - $this->assertEquals(202, $deployment['headers']['status-code']); - - $this->assertDeployment($function['body']['$id'], $deploymentId); - - // Create user to trigger event + // Create user as an event trigger $user = $this->client->call(Client::METHOD_POST, '/users', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -1946,111 +1489,57 @@ class FunctionsCustomServerTest extends Scope 'userId' => 'unique()', 'name' => 'Event User' ]); - - $userId = $user['body']['$id']; + $userId = $user['body']['$id'] ?? ''; $this->assertEquals(201, $user['headers']['status-code']); - $execution = null; - $this->assertEventually(function () use ($functionId, &$execution) { - $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ]); + $this->assertEventually(function () use ($functionId, $userId) { + $executions = $this->listExecutions($functionId); + $lastExecution = $executions['body']['executions'][0]; + $this->assertEquals('completed', $lastExecution['status']); + $this->assertEquals(204, $lastExecution['responseStatusCode']); + $this->assertStringContainsString($userId, $lastExecution['logs']); + $this->assertStringContainsString('Event User', $lastExecution['logs']); + }, 10000, 500); - if (empty($executions['body']['executions'])) { - throw new \Exception('No executions found yet'); - } + $this->cleanupFunction($functionId); - $execution = $executions['body']['executions'][0]; - $this->assertEquals('completed', $execution['status']); - }, 30000, 1000); - - $this->assertEquals(204, $execution['responseStatusCode']); - $this->assertStringContainsString($userId, $execution['logs']); - $this->assertStringContainsString('Event User', $execution['logs']); - - // Cleanup : Delete function - $response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, [ + // Cleanup user + $user = $this->client->call(Client::METHOD_DELETE, '/users/' . $userId, [ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'], ], []); - - $this->assertEquals(204, $response['headers']['status-code']); - - // Cleanup : Delete user - $response = $this->client->call(Client::METHOD_DELETE, '/users/' . $userId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], []); - - $this->assertEquals(204, $response['headers']['status-code']); + $this->assertEquals(204, $user['headers']['status-code']); } public function testScopes() { - $timeout = 15; - $code = realpath(__DIR__ . '/../../../resources/functions') . "/php-scopes/code.tar.gz"; - $this->packageCode('php-scopes'); - - $function = $this->client->call(Client::METHOD_POST, '/functions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + $functionId = $this->setupFunction([ 'functionId' => ID::unique(), 'name' => 'Test PHP Scopes executions', 'commands' => 'composer update --no-interaction --ignore-platform-reqs --optimize-autoloader --prefer-dist --no-dev', 'runtime' => 'php-8.0', 'entrypoint' => 'index.php', 'scopes' => ['users.read'], - 'timeout' => $timeout, + 'timeout' => 15, ]); - $functionId = $function['body']['$id'] ?? ''; - - $this->assertEquals(201, $function['headers']['status-code']); - - $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', array_merge([ - 'content-type' => 'multipart/form-data', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + $deploymentId = $this->setupDeployment($functionId, [ 'entrypoint' => 'index.php', 'commands' => 'sh setup.sh && composer install', - 'code' => new CURLFile($code, 'application/x-gzip', basename($code)), - 'activate' => true + 'code' => $this->packageFunction('php-scopes'), + 'activate' => true, ]); - $deploymentId = $deployment['body']['$id'] ?? ''; - $this->assertEquals(202, $deployment['headers']['status-code']); - - $this->assertDeployment($function['body']['$id'], $deploymentId); - - $deployment = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/deployments/' . $deploymentId, array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), []); + $deployment = $this->getDeployment($functionId, $deploymentId); $this->assertEquals(200, $deployment['headers']['status-code']); $this->assertStringContainsStringIgnoringCase("200 OK", $deployment['body']['buildLogs']); $this->assertStringContainsStringIgnoringCase('"total":', $deployment['body']['buildLogs']); $this->assertStringContainsStringIgnoringCase('"users":', $deployment['body']['buildLogs']); - $deployment = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), []); - - $this->assertEquals(200, $deployment['headers']['status-code']); - - // Wait a little for activation to finish - sleep(5); - - $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'async' => false + $execution = $this->createExecution($functionId, [ + 'async' => false, ]); $this->assertEquals(201, $execution['headers']['status-code']); @@ -2060,81 +1549,44 @@ class FunctionsCustomServerTest extends Scope $this->assertNotEmpty($execution['body']['responseBody']); $this->assertStringContainsString("total", $execution['body']['responseBody']); - $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'async' => true + $execution = $this->createExecution($functionId, [ + 'async' => true, ]); - + $executionId = $execution['body']['$id'] ?? ''; $this->assertEquals(202, $execution['headers']['status-code']); $this->assertNotEmpty($execution['body']['$id']); - \sleep(10); + $this->assertEventually(function () use ($functionId, $executionId) { + $execution = $this->getExecution($functionId, $executionId); - $execution = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions/' . $execution['body']['$id'], array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), []); + $this->assertEquals(200, $execution['headers']['status-code']); + $this->assertEquals('completed', $execution['body']['status']); + $this->assertEquals(200, $execution['body']['responseStatusCode']); + $this->assertGreaterThan(0, $execution['body']['duration']); + $this->assertNotEmpty($execution['body']['responseBody']); + $this->assertStringContainsString("total", $execution['body']['responseBody']); + }, 5000, 250); - $this->assertEquals(200, $execution['headers']['status-code']); - $this->assertEquals('completed', $execution['body']['status']); - $this->assertEquals(200, $execution['body']['responseStatusCode']); - $this->assertGreaterThan(0, $execution['body']['duration']); - $this->assertNotEmpty($execution['body']['logs']); - $this->assertStringContainsString("total", $execution['body']['logs']); - - // Cleanup : Delete function - $response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], []); - - $this->assertEquals(204, $response['headers']['status-code']); + $this->cleanupFunction($functionId); } public function testCookieExecution() { - $timeout = 15; - $code = realpath(__DIR__ . '/../../../resources/functions') . "/php-cookie/code.tar.gz"; - $this->packageCode('php-cookie'); - - $function = $this->client->call(Client::METHOD_POST, '/functions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + $functionId = $this->setupFunction([ 'functionId' => ID::unique(), 'name' => 'Test PHP Cookie executions', 'runtime' => 'php-8.0', 'entrypoint' => 'index.php', - 'timeout' => $timeout, + 'timeout' => 15, ]); - - $functionId = $function['body']['$id'] ?? ''; - - $this->assertEquals(201, $function['headers']['status-code']); - - $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', array_merge([ - 'content-type' => 'multipart/form-data', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + $this->setupDeployment($functionId, [ 'entrypoint' => 'index.php', - 'code' => new CURLFile($code, 'application/x-gzip', basename($code)), + 'code' => $this->packageFunction('php-cookie'), 'activate' => true ]); - $deploymentId = $deployment['body']['$id'] ?? ''; - $this->assertEquals(202, $deployment['headers']['status-code']); - - $this->assertDeployment($function['body']['$id'], $deploymentId); - $cookie = 'cookieName=cookieValue; cookie2=value2; cookie3=value=3; cookie4=val:ue4; cookie5=value5'; - - $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + $execution = $this->createExecution($functionId, [ 'async' => false, 'headers' => [ 'cookie' => $cookie @@ -2147,38 +1599,20 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals($cookie, $execution['body']['responseBody']); $this->assertGreaterThan(0, $execution['body']['duration']); - // Cleanup : Delete function - $response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], []); - - $this->assertEquals(204, $response['headers']['status-code']); + $this->cleanupFunction($functionId); } public function testFunctionsDomain() { - $timeout = 15; - $code = realpath(__DIR__ . '/../../../resources/functions') . "/php-cookie/code.tar.gz"; - $this->packageCode('php-cookie'); - - $function = $this->client->call(Client::METHOD_POST, '/functions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + $functionId = $this->setupFunction([ 'functionId' => ID::unique(), 'name' => 'Test PHP Cookie executions', 'runtime' => 'php-8.0', 'entrypoint' => 'index.php', - 'timeout' => $timeout, + 'timeout' => 15, 'execute' => ['any'] ]); - $functionId = $function['body']['$id'] ?? ''; - - $this->assertEquals(201, $function['headers']['status-code']); - $rules = $this->client->call(Client::METHOD_GET, '/proxy/rules', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -2196,20 +1630,12 @@ class FunctionsCustomServerTest extends Scope $domain = $rules['body']['rules'][0]['domain']; - $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', array_merge([ - 'content-type' => 'multipart/form-data', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + $this->setupDeployment($functionId, [ 'entrypoint' => 'index.php', - 'code' => new CURLFile($code, 'application/x-gzip', basename($code)), + 'code' => $this->packageFunction('php-cookie'), 'activate' => true ]); - $deploymentId = $deployment['body']['$id'] ?? ''; - $this->assertEquals(202, $deployment['headers']['status-code']); - - $this->assertDeployment($function['body']['$id'], $deploymentId); - $cookie = 'cookieName=cookieValue; cookie2=value2; cookie3=value=3; cookie4=val:ue4; cookie5=value5'; $proxyClient = new Client(); @@ -2227,64 +1653,31 @@ class FunctionsCustomServerTest extends Scope // Await Aggregation sleep(App::getEnv('_APP_USAGE_AGGREGATION_INTERVAL', 30)); - $tries = 0; - while (true) { - try { - $response = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/usage', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'] - ], $this->getHeaders()), [ - 'range' => '24h' - ]); + $this->assertEventually(function () use ($functionId) { + $response = $this->getFunctionUsage($functionId, [ + 'range' => '24h' + ]); - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals(19, count($response['body'])); - $this->assertEquals('24h', $response['body']['range']); - $this->assertEquals(1, $response['body']['executionsTotal']); + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals(19, count($response['body'])); + $this->assertEquals('24h', $response['body']['range']); + $this->assertEquals(1, $response['body']['executionsTotal']); + }, 25000, 1000); - break; - } catch (ExpectationFailedException $th) { - if ($tries >= 5) { - throw $th; - } else { - $tries++; - sleep(5); - } - } - } - - // Cleanup : Delete function - $response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], []); - - $this->assertEquals(204, $response['headers']['status-code']); + $this->cleanupFunction($functionId); } public function testFunctionsDomainBinaryResponse() { - $timeout = 15; - $code = realpath(__DIR__ . '/../../../resources/functions') . "/php-binary-response/code.tar.gz"; - $this->packageCode('php-binary-response'); - - $function = $this->client->call(Client::METHOD_POST, '/functions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + $functionId = $this->setupFunction([ 'functionId' => ID::unique(), 'name' => 'Test PHP Binary executions', 'runtime' => 'php-8.0', 'entrypoint' => 'index.php', - 'timeout' => $timeout, + 'timeout' => 15, 'execute' => ['any'] ]); - $functionId = $function['body']['$id'] ?? ''; - - $this->assertEquals(201, $function['headers']['status-code']); - $rules = $this->client->call(Client::METHOD_GET, '/proxy/rules', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -2302,20 +1695,11 @@ class FunctionsCustomServerTest extends Scope $domain = $rules['body']['rules'][0]['domain']; - $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', array_merge([ - 'content-type' => 'multipart/form-data', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + $this->setupDeployment($functionId, [ 'entrypoint' => 'index.php', - 'code' => new CURLFile($code, 'application/x-gzip', basename($code)), - 'activate' => true + 'code' => $this->packageFunction('php-binary-response'), ]); - $deploymentId = $deployment['body']['$id'] ?? ''; - $this->assertEquals(202, $deployment['headers']['status-code']); - - $this->assertDeployment($function['body']['$id'], $deploymentId); - $proxyClient = new Client(); $proxyClient->setEndpoint('http://' . $domain); @@ -2329,38 +1713,20 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals(10, $bytes['byte2']); $this->assertEquals(255, $bytes['byte3']); - // Cleanup : Delete function - $response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], []); - - $this->assertEquals(204, $response['headers']['status-code']); + $this->cleanupFunction($functionId); } public function testFunctionsDomainBinaryRequest() { - $timeout = 15; - $code = realpath(__DIR__ . '/../../../resources/functions') . "/php-binary-request/code.tar.gz"; - $this->packageCode('php-binary-request'); - - $function = $this->client->call(Client::METHOD_POST, '/functions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + $functionId = $this->setupFunction([ 'functionId' => ID::unique(), 'name' => 'Test PHP Binary executions', 'runtime' => 'php-8.0', 'entrypoint' => 'index.php', - 'timeout' => $timeout, + 'timeout' => 15, 'execute' => ['any'] ]); - $functionId = $function['body']['$id'] ?? ''; - - $this->assertEquals(201, $function['headers']['status-code']); - $rules = $this->client->call(Client::METHOD_GET, '/proxy/rules', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -2378,20 +1744,12 @@ class FunctionsCustomServerTest extends Scope $domain = $rules['body']['rules'][0]['domain']; - $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', array_merge([ - 'content-type' => 'multipart/form-data', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + $this->setupDeployment($functionId, [ 'entrypoint' => 'index.php', - 'code' => new CURLFile($code, 'application/x-gzip', basename($code)), + 'code' => $this->packageFunction('php-binary-request'), 'activate' => true ]); - $deploymentId = $deployment['body']['$id'] ?? ''; - $this->assertEquals(202, $deployment['headers']['status-code']); - - $this->assertDeployment($function['body']['$id'], $deploymentId); - $proxyClient = new Client(); $proxyClient->setEndpoint('http://' . $domain); @@ -2402,19 +1760,12 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals(200, $response['headers']['status-code']); $this->assertEquals(\md5($bytes), $response['body']); - // Cleanup : Delete function - $response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], []); - - $this->assertEquals(204, $response['headers']['status-code']); + $this->cleanupFunction($functionId); } public function testCreateFunctionWithResponseFormatHeader() { - $response = $this->client->call(Client::METHOD_POST, '/functions', array_merge([ + $function = $this->client->call(Client::METHOD_POST, '/functions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-response-format' => '1.5.0', // add response format header @@ -2426,29 +1777,16 @@ class FunctionsCustomServerTest extends Scope 'timeout' => 15, ]); - $this->assertEquals(201, $response['headers']['status-code']); + $this->assertEquals(201, $function['headers']['status-code']); - // Cleanup : Delete function - $response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $response['body']['$id'], [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], []); + $functionId = $function['body']['$id'] ?? ''; - $this->assertEquals(204, $response['headers']['status-code']); + $this->cleanupFunction($functionId); } public function testFunctionLogging() { - // Preparations: Create Function - $folder = 'node'; - $code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz"; - $this->packageCode($folder); - - $function = $this->client->call(Client::METHOD_POST, '/functions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + $function = $this->createFunction([ 'functionId' => ID::unique(), 'runtime' => 'node-18.0', 'name' => 'Logging Test', @@ -2461,38 +1799,22 @@ class FunctionsCustomServerTest extends Scope $this->assertFalse($function['body']['logging']); $this->assertNotEmpty($function['body']['$id']); - $functionId = $function['body']['$id']; + $functionId = $functionId = $function['body']['$id'] ?? ''; - $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', array_merge([ - 'content-type' => 'multipart/form-data', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'code' => new CURLFile($code, 'application/x-gzip', basename($code)), + $this->setupDeployment($functionId, [ + 'code' => $this->packageFunction('node'), 'activate' => true ]); - $this->assertEquals(202, $deployment['headers']['status-code']); - $this->assertNotEmpty($deployment['body']['$id']); - - $deploymentId = $deployment['body']['$id'] ?? ''; - - $this->assertDeployment($functionId, $deploymentId); - // Sync Executions test - $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), []); + $execution = $this->createExecution($functionId); $this->assertEquals(201, $execution['headers']['status-code']); $this->assertEmpty($execution['body']['logs']); $this->assertEmpty($execution['body']['errors']); // Async Executions test - $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + $execution = $this->createExecution($functionId, [ 'async' => true ]); @@ -2501,19 +1823,16 @@ class FunctionsCustomServerTest extends Scope $this->assertEmpty($execution['body']['errors']); $this->assertNotEmpty($execution['body']['$id']); - $executionId = $execution['body']['$id']; + $executionId = $execution['body']['$id'] ?? ''; - sleep(5); + $this->assertEventually(function () use ($functionId, $executionId) { + $execution = $this->getExecution($functionId, $executionId); - $execution = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions/' . $executionId, array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $this->assertEquals(200, $execution['headers']['status-code']); - $this->assertEquals('completed', $execution['body']['status']); - $this->assertEmpty($execution['body']['logs']); - $this->assertEmpty($execution['body']['errors']); + $this->assertEquals(200, $execution['headers']['status-code']); + $this->assertEquals('completed', $execution['body']['status']); + $this->assertEmpty($execution['body']['logs']); + $this->assertEmpty($execution['body']['errors']); + }, 5000, 250); // Domain Executions test $rules = $this->client->call(Client::METHOD_GET, '/proxy/rules', array_merge([ @@ -2541,10 +1860,7 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals(200, $response['headers']['status-code']); - $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + $executions = $this->listExecutions($functionId, [ 'queries' => [ Query::limit(1)->toString(), Query::orderDesc('$id')->toString(), @@ -2557,10 +1873,7 @@ class FunctionsCustomServerTest extends Scope $this->assertEmpty($executions['body']['executions'][0]['errors']); // Ensure executions count - $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); + $executions = $this->listExecutions($functionId); $this->assertEquals(200, $executions['headers']['status-code']); $this->assertCount(3, $executions['body']['executions']); @@ -2571,13 +1884,6 @@ class FunctionsCustomServerTest extends Scope $this->assertEmpty($execution['errors']); } - // Cleanup : Delete function - $response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], []); - - $this->assertEquals(204, $response['headers']['status-code']); + $this->cleanupFunction($functionId); } } From 32657e22cc1e8d3ade1dca3fb52962277a4fe54f Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Thu, 19 Sep 2024 20:34:38 +0100 Subject: [PATCH 06/33] fix: conflicts --- tests/e2e/Services/Functions/FunctionsBase.php | 2 +- tests/e2e/Services/Functions/FunctionsCustomServerTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/e2e/Services/Functions/FunctionsBase.php b/tests/e2e/Services/Functions/FunctionsBase.php index 7cfa04b0ac..04039e3acc 100644 --- a/tests/e2e/Services/Functions/FunctionsBase.php +++ b/tests/e2e/Services/Functions/FunctionsBase.php @@ -146,7 +146,7 @@ trait FunctionsBase $code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz"; if (!file_exists($code)) { - Console::execute('cd ' . realpath(__DIR__ . "/../../../resources/functions") . "/$folder && tar --exclude code.tar.gz -czf code.tar.gz .", '', $this->stdout, $this->stderr); + Console::execute('cd ' . realpath(__DIR__ . "/../../../resources/functions") . "/$folder && tar --exclude code.tar.gz -czf code.tar.gz .", '', $this->output, $this->output); } if (filesize($code) > 1024 * 1024 * 5) { throw new \Exception('Code package is too large. Use the chunked upload method instead.'); diff --git a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php index e383abe36f..1796871c26 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php @@ -513,7 +513,7 @@ class FunctionsCustomServerTest extends Scope $folder = 'php-large'; $code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz"; - Console::execute('cd ' . realpath(__DIR__ . "/../../../resources/functions") . "/$folder && tar --exclude code.tar.gz -czf code.tar.gz .", '', $this->stdout, $this->stderr); + Console::execute('cd ' . realpath(__DIR__ . "/../../../resources/functions") . "/$folder && tar --exclude code.tar.gz -czf code.tar.gz .", '', $this->output, $this->output); $chunkSize = 5 * 1024 * 1024; $handle = @fopen($code, "rb"); From 504711845aae015cbc487e000f5b944a33094fc9 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Thu, 19 Sep 2024 20:46:05 +0100 Subject: [PATCH 07/33] fix: stdout --- tests/e2e/Services/Functions/FunctionsBase.php | 2 +- tests/e2e/Services/Functions/FunctionsCustomServerTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/e2e/Services/Functions/FunctionsBase.php b/tests/e2e/Services/Functions/FunctionsBase.php index 04039e3acc..d2bb19dea2 100644 --- a/tests/e2e/Services/Functions/FunctionsBase.php +++ b/tests/e2e/Services/Functions/FunctionsBase.php @@ -146,7 +146,7 @@ trait FunctionsBase $code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz"; if (!file_exists($code)) { - Console::execute('cd ' . realpath(__DIR__ . "/../../../resources/functions") . "/$folder && tar --exclude code.tar.gz -czf code.tar.gz .", '', $this->output, $this->output); + Console::execute('cd ' . realpath(__DIR__ . "/../../../resources/functions") . "/$folder && tar --exclude code.tar.gz -czf code.tar.gz .", '', $this->output); } if (filesize($code) > 1024 * 1024 * 5) { throw new \Exception('Code package is too large. Use the chunked upload method instead.'); diff --git a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php index 1796871c26..7c4cd6d1ee 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php @@ -513,7 +513,7 @@ class FunctionsCustomServerTest extends Scope $folder = 'php-large'; $code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz"; - Console::execute('cd ' . realpath(__DIR__ . "/../../../resources/functions") . "/$folder && tar --exclude code.tar.gz -czf code.tar.gz .", '', $this->output, $this->output); + Console::execute('cd ' . realpath(__DIR__ . "/../../../resources/functions") . "/$folder && tar --exclude code.tar.gz -czf code.tar.gz .", '', $this->output); $chunkSize = 5 * 1024 * 1024; $handle = @fopen($code, "rb"); From 65c0db32852f3eb41a560034b5fb64876d67e859 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Thu, 19 Sep 2024 20:58:09 +0100 Subject: [PATCH 08/33] fix: package function --- tests/e2e/Services/Functions/FunctionsBase.php | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/tests/e2e/Services/Functions/FunctionsBase.php b/tests/e2e/Services/Functions/FunctionsBase.php index d2bb19dea2..5ed87b4895 100644 --- a/tests/e2e/Services/Functions/FunctionsBase.php +++ b/tests/e2e/Services/Functions/FunctionsBase.php @@ -141,18 +141,20 @@ trait FunctionsBase return $executions; } - protected function packageFunction(string $folder = 'php'): CURLFile + protected function packageFunction(string $function = 'php'): CURLFile { - $code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz"; + $folderPath = realpath(__DIR__ . '/../../../resources/functions') . "/$function"; + $tarPath = "$folderPath/code.tar.gz"; - if (!file_exists($code)) { - Console::execute('cd ' . realpath(__DIR__ . "/../../../resources/functions") . "/$folder && tar --exclude code.tar.gz -czf code.tar.gz .", '', $this->output); + if (!file_exists($tarPath)) { + Console::execute("cd $folderPath && tar --exclude code.tar.gz -czf code.tar.gz .", '', $this->output); } - if (filesize($code) > 1024 * 1024 * 5) { + + if (filesize($tarPath) > 1024 * 1024 * 5) { throw new \Exception('Code package is too large. Use the chunked upload method instead.'); } - return new CURLFile($code, 'application/x-gzip', \basename($code)); + return new CURLFile($tarPath, 'application/x-gzip', \basename($tarPath)); } protected function createDeployment( From cb0416be7bd294725c4c6c455c44c05d973f38c9 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Thu, 19 Sep 2024 21:08:04 +0100 Subject: [PATCH 09/33] chore: fmt --- tests/e2e/General/UsageTest.php | 7 ++----- .../Functions/FunctionsCustomServerTest.php | 2 +- tests/e2e/Services/GraphQL/Base.php | 19 +++++++++++++++---- .../Services/GraphQL/FunctionsClientTest.php | 7 +------ .../Services/GraphQL/FunctionsServerTest.php | 7 +------ .../Realtime/RealtimeConsoleClientTest.php | 9 +-------- 6 files changed, 21 insertions(+), 30 deletions(-) diff --git a/tests/e2e/General/UsageTest.php b/tests/e2e/General/UsageTest.php index d3623acffc..69460aa4cd 100644 --- a/tests/e2e/General/UsageTest.php +++ b/tests/e2e/General/UsageTest.php @@ -629,9 +629,6 @@ class UsageTest extends Scope $this->assertEquals(201, $response['headers']['status-code']); $this->assertNotEmpty($response['body']['$id']); - $code = realpath(__DIR__ . '/../../resources/functions') . "/php/code.tar.gz"; - $this->packageCode('php'); - $response = $this->client->call( Client::METHOD_POST, '/functions/' . $functionId . '/deployments', @@ -641,8 +638,8 @@ class UsageTest extends Scope ], $this->getHeaders()), [ 'entrypoint' => 'index.php', - 'code' => new CURLFile($code, 'application/x-gzip', \basename($code)), - 'activate' => true + 'code' => $this->packageFunction('php'), + 'activate' => true, ] ); diff --git a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php index 7c4cd6d1ee..4ff6639864 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php @@ -775,7 +775,7 @@ class FunctionsCustomServerTest extends Scope $matchingDeployment = array_filter( $deployments['body']['deployments'], - fn($deployment) => $deployment['$id'] === $deploymentId + fn ($deployment) => $deployment['$id'] === $deploymentId ); $this->assertNotEmpty($matchingDeployment, "Deployment with ID {$deploymentId} not found"); diff --git a/tests/e2e/Services/GraphQL/Base.php b/tests/e2e/Services/GraphQL/Base.php index b77f006bf8..449275f102 100644 --- a/tests/e2e/Services/GraphQL/Base.php +++ b/tests/e2e/Services/GraphQL/Base.php @@ -2,6 +2,7 @@ namespace Tests\E2E\Services\GraphQL; +use CURLFile; use Utopia\CLI\Console; trait Base @@ -2493,11 +2494,21 @@ trait Base } // Function-related methods - protected string $stdout = ''; - protected string $stderr = ''; + protected string $output = ''; - protected function packageCode($folder): void + protected function packageFunction(string $function = 'php'): CURLFile { - Console::execute('cd ' . realpath(__DIR__ . "/../../../resources/functions") . "/$folder && tar --exclude code.tar.gz -czf code.tar.gz .", '', $this->stdout); + $folderPath = realpath(__DIR__ . '/../../../resources/functions') . "/$function"; + $tarPath = "$folderPath/code.tar.gz"; + + if (!file_exists($tarPath)) { + Console::execute("cd $folderPath && tar --exclude code.tar.gz -czf code.tar.gz .", '', $this->output); + } + + if (filesize($tarPath) > 1024 * 1024 * 5) { + throw new \Exception('Code package is too large. Use the chunked upload method instead.'); + } + + return new CURLFile($tarPath, 'application/x-gzip', \basename($tarPath)); } } diff --git a/tests/e2e/Services/GraphQL/FunctionsClientTest.php b/tests/e2e/Services/GraphQL/FunctionsClientTest.php index 3f0ee1966a..e7e8421254 100644 --- a/tests/e2e/Services/GraphQL/FunctionsClientTest.php +++ b/tests/e2e/Services/GraphQL/FunctionsClientTest.php @@ -2,7 +2,6 @@ namespace Tests\E2E\Services\GraphQL; -use CURLFile; use Tests\E2E\Client; use Tests\E2E\Scopes\ProjectCustom; use Tests\E2E\Scopes\Scope; @@ -83,10 +82,6 @@ class FunctionsClientTest extends Scope $projectId = $this->getProject()['$id']; $query = $this->getQuery(self::$CREATE_DEPLOYMENT); - $folder = 'php'; - $code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz"; - $this->packageCode($folder); - $gqlPayload = [ 'operations' => \json_encode([ 'query' => $query, @@ -99,7 +94,7 @@ class FunctionsClientTest extends Scope 'map' => \json_encode([ 'code' => ["variables.code"] ]), - 'code' => new CURLFile($code, 'application/gzip', 'code.tar.gz'), + 'code' => $this->packageFunction('php') ]; $deployment = $this->client->call(Client::METHOD_POST, '/graphql', [ diff --git a/tests/e2e/Services/GraphQL/FunctionsServerTest.php b/tests/e2e/Services/GraphQL/FunctionsServerTest.php index 25a671fa1c..c3606244c4 100644 --- a/tests/e2e/Services/GraphQL/FunctionsServerTest.php +++ b/tests/e2e/Services/GraphQL/FunctionsServerTest.php @@ -2,7 +2,6 @@ namespace Tests\E2E\Services\GraphQL; -use CURLFile; use Tests\E2E\Client; use Tests\E2E\Scopes\ProjectCustom; use Tests\E2E\Scopes\Scope; @@ -82,10 +81,6 @@ class FunctionsServerTest extends Scope $projectId = $this->getProject()['$id']; $query = $this->getQuery(self::$CREATE_DEPLOYMENT); - $folder = 'php'; - $code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz"; - $this->packageCode($folder); - $gqlPayload = [ 'operations' => \json_encode([ 'query' => $query, @@ -98,7 +93,7 @@ class FunctionsServerTest extends Scope 'map' => \json_encode([ 'code' => ["variables.code"] ]), - 'code' => new CURLFile($code, 'application/gzip', 'code.tar.gz'), + 'code' => $this->packageFunction('php'), ]; $deployment = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([ diff --git a/tests/e2e/Services/Realtime/RealtimeConsoleClientTest.php b/tests/e2e/Services/Realtime/RealtimeConsoleClientTest.php index 6ab2874f8e..b1777cdba9 100644 --- a/tests/e2e/Services/Realtime/RealtimeConsoleClientTest.php +++ b/tests/e2e/Services/Realtime/RealtimeConsoleClientTest.php @@ -2,7 +2,6 @@ namespace Tests\E2E\Services\Realtime; -use CURLFile; use Tests\E2E\Client; use Tests\E2E\Scopes\ProjectCustom; use Tests\E2E\Scopes\Scope; @@ -506,21 +505,15 @@ class RealtimeConsoleClientTest extends Scope * Test Create Deployment */ - $folder = 'php'; - $code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz"; - $this->packageCode($folder); - $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', array_merge([ 'content-type' => 'multipart/form-data', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ 'entrypoint' => 'index.php', - 'code' => new CURLFile($code, 'application/x-gzip', \basename($code)), + 'code' => $this->packageFunction('php'), 'activate' => true ]); - $deploymentId = $deployment['body']['$id'] ?? ''; - $this->assertEquals(202, $deployment['headers']['status-code']); $response = json_decode($client->receive(), true); From 410a35deb510535de565915921afa6c09add1075 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Thu, 19 Sep 2024 21:29:18 +0100 Subject: [PATCH 10/33] fix: spacing --- .../Functions/FunctionsCustomServerTest.php | 123 ++++++++++++++---- 1 file changed, 100 insertions(+), 23 deletions(-) diff --git a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php index 4ff6639864..47e80efde9 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php @@ -38,7 +38,9 @@ class FunctionsCustomServerTest extends Scope 'schedule' => '0 0 1 1 *', 'timeout' => 10, ]); + $functionId = $functionId = $function['body']['$id'] ?? ''; + $dateValidator = new DatetimeValidator(); $this->assertEquals(201, $function['headers']['status-code']); $this->assertNotEmpty($function['body']['$id']); @@ -66,6 +68,7 @@ class FunctionsCustomServerTest extends Scope 'key' => 'funcKey3', 'value' => 'funcValue3', ]); + $this->assertEquals(201, $variable['headers']['status-code']); $this->assertEquals(201, $variable2['headers']['status-code']); $this->assertEquals(201, $variable3['headers']['status-code']); @@ -87,6 +90,7 @@ class FunctionsCustomServerTest extends Scope $functions = $this->listFunctions([ 'search' => $data['functionId'] ]); + $this->assertEquals($functions['headers']['status-code'], 200); $this->assertCount(1, $functions['body']['functions']); $this->assertEquals($functions['body']['functions'][0]['name'], 'Test'); @@ -97,6 +101,7 @@ class FunctionsCustomServerTest extends Scope Query::limit(1)->toString(), ], ]); + $this->assertEquals($functions['headers']['status-code'], 200); $this->assertCount(1, $functions['body']['functions']); @@ -106,6 +111,7 @@ class FunctionsCustomServerTest extends Scope Query::offset(1)->toString(), ], ]); + $this->assertEquals($functions['headers']['status-code'], 200); $this->assertCount(0, $functions['body']['functions']); @@ -115,6 +121,7 @@ class FunctionsCustomServerTest extends Scope Query::equal('enabled', [true])->toString(), ], ]); + $this->assertEquals($functions['headers']['status-code'], 200); $this->assertCount(1, $functions['body']['functions']); @@ -124,6 +131,7 @@ class FunctionsCustomServerTest extends Scope Query::equal('enabled', [false])->toString(), ], ]); + $this->assertEquals($functions['headers']['status-code'], 200); $this->assertCount(0, $functions['body']['functions']); @@ -131,6 +139,7 @@ class FunctionsCustomServerTest extends Scope $functions = $this->listFunctions([ 'search' => 'Test' ]); + $this->assertEquals($functions['headers']['status-code'], 200); $this->assertCount(1, $functions['body']['functions']); $this->assertEquals($functions['body']['functions'][0]['$id'], $data['functionId']); @@ -139,6 +148,7 @@ class FunctionsCustomServerTest extends Scope $functions = $this->listExecutions([ 'search' => 'php-8.0' ]); + $this->assertEquals($functions['headers']['status-code'], 200); $this->assertCount(1, $functions['body']['functions']); $this->assertEquals($functions['body']['functions'][0]['$id'], $data['functionId']); @@ -160,6 +170,7 @@ class FunctionsCustomServerTest extends Scope ]); $functions = $this->listFunctions(); + $this->assertEquals($functions['headers']['status-code'], 200); $this->assertEquals($functions['body']['total'], 2); $this->assertIsArray($functions['body']['functions']); @@ -172,6 +183,7 @@ class FunctionsCustomServerTest extends Scope Query::cursorAfter(new Document(['$id' => $functions['body']['functions'][0]['$id']]))->toString(), ], ]); + $this->assertEquals($functions['headers']['status-code'], 200); $this->assertCount(1, $functions['body']['functions']); $this->assertEquals($functions['body']['functions'][0]['name'], 'Test 2'); @@ -181,6 +193,7 @@ class FunctionsCustomServerTest extends Scope Query::cursorBefore(new Document(['$id' => $functions['body']['functions'][1]['$id']]))->toString(), ], ]); + $this->assertEquals($functions['headers']['status-code'], 200); $this->assertCount(1, $functions['body']['functions']); $this->assertEquals($functions['body']['functions'][0]['name'], 'Test'); @@ -207,6 +220,7 @@ class FunctionsCustomServerTest extends Scope * Test for SUCCESS */ $function = $this->getFunction($data['functionId']); + $this->assertEquals($function['headers']['status-code'], 200); $this->assertEquals($function['body']['name'], 'Test'); @@ -214,6 +228,7 @@ class FunctionsCustomServerTest extends Scope * Test for FAILURE */ $function = $this->getFunction('x'); + $this->assertEquals($function['headers']['status-code'], 404); return $data; @@ -242,10 +257,11 @@ class FunctionsCustomServerTest extends Scope 'entrypoint' => 'index.php', ]); + $dateValidator = new DatetimeValidator(); + $this->assertEquals(200, $function['headers']['status-code']); $this->assertNotEmpty($function['body']['$id']); $this->assertEquals('Test1', $function['body']['name']); - $dateValidator = new DatetimeValidator(); $this->assertEquals(true, $dateValidator->isValid($function['body']['$createdAt'])); $this->assertEquals(true, $dateValidator->isValid($function['body']['$updatedAt'])); $this->assertEquals('', $function['body']['deployment']); @@ -261,11 +277,8 @@ class FunctionsCustomServerTest extends Scope 'key' => 'GLOBAL_VARIABLE', 'value' => 'Global Variable Value', ]); - $this->assertEquals(201, $variable['headers']['status-code']); - /** - * Test for FAILURE - */ + $this->assertEquals(201, $variable['headers']['status-code']); return $data; } @@ -296,13 +309,15 @@ class FunctionsCustomServerTest extends Scope 'code' => $this->packageFunction('php'), 'activate' => true ]); - $deploymentId = $deployment['body']['$id'] ?? ''; + $this->assertEquals(202, $deployment['headers']['status-code']); + $deploymentId = $deployment['body']['$id'] ?? ''; + $this->assertEventually(function () use ($functionId, $deploymentId) { $deployment = $this->getDeployment($functionId, $deploymentId); $this->assertEquals(200, $deployment['headers']['status-code']); - $this->assertEquals('completed', $deployment['body']['status']); + $this->assertEquals('ready', $deployment['body']['status']); $this->assertEquals('cli', $deployment['body']['type']); }, 500000, 1000); } @@ -338,23 +353,28 @@ class FunctionsCustomServerTest extends Scope 'templateVersion' => $starterTemplate['body']['providerVersion'], ] ); - $functionId = $functionId = $function['body']['$id'] ?? ''; + $this->assertEquals(201, $function['headers']['status-code']); $this->assertNotEmpty($function['body']['$id']); + $functionId = $functionId = $function['body']['$id'] ?? ''; + $deployments = $this->listDeployments($functionId); + $this->assertEquals(200, $deployments['headers']['status-code']); $this->assertEquals(1, $deployments['body']['total']); $lastDeployment = $deployments['body']['deployments'][0]; + $this->assertNotEmpty($lastDeployment['$id']); $this->assertEquals(0, $lastDeployment['size']); $deploymentId = $lastDeployment['$id']; + $this->assertEventually(function () use ($functionId, $deploymentId) { $deployment = $this->getDeployment($functionId, $deploymentId); $this->assertEquals(200, $deployment['headers']['status-code']); - $this->assertEquals('completed', $deployment['body']['status']); + $this->assertEquals('ready', $deployment['body']['status']); $this->assertEquals('cli', $deployment['body']['type']); }, 500000, 1000); @@ -366,6 +386,7 @@ class FunctionsCustomServerTest extends Scope $execution = $this->createExecution($functionId, [ 'path' => '/ping', ]); + $this->assertEquals(201, $execution['headers']['status-code']); $this->assertEquals("completed", $execution['body']['status']); $this->assertEquals(200, $execution['body']['responseStatusCode']); @@ -378,10 +399,12 @@ class FunctionsCustomServerTest extends Scope 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'], ], $this->getHeaders()), []); + $this->assertEquals(200, $users['headers']['status-code']); $this->assertIsInt($users['body']['total']); $totalUsers = $users['body']['total']; + $this->assertStringContainsString("Total users: " . $totalUsers, $execution['body']['logs']); // Execute function again but async @@ -389,13 +412,16 @@ class FunctionsCustomServerTest extends Scope 'path' => '/ping', 'async' => true ]); - $executionId = $execution['body']['$id'] ?? ''; + $this->assertEquals(202, $execution['headers']['status-code']); $this->assertNotEmpty($execution['body']['$id']); $this->assertEquals('waiting', $execution['body']['status']); + $executionId = $execution['body']['$id'] ?? ''; + $this->assertEventually(function () use ($functionId, $executionId, $totalUsers) { $execution = $this->getExecution($functionId, $executionId); + $this->assertEquals(200, $execution['headers']['status-code']); $this->assertEquals(200, $execution['body']['responseStatusCode']); $this->assertEquals('completed', $execution['body']['status']); @@ -405,7 +431,6 @@ class FunctionsCustomServerTest extends Scope }, 100000, 250); $function = $this->deleteFunction($functionId); - $this->assertEquals(204, $function['headers']['status-code']); } /** @@ -417,34 +442,39 @@ class FunctionsCustomServerTest extends Scope * Test for SUCCESS */ $functionId = $data['functionId']; + $deployment = $this->createDeployment($functionId, [ 'code' => $this->packageFunction('php'), 'activate' => true ]); - $deploymentId = $deployment['body']['$id'] ?? ''; $this->assertEquals(202, $deployment['headers']['status-code']); $this->assertNotEmpty($deployment['body']['$id']); $this->assertEquals(true, (new DatetimeValidator())->isValid($deployment['body']['$createdAt'])); $this->assertEquals('index.php', $deployment['body']['entrypoint']); + $deploymentId = $deployment['body']['$id'] ?? ''; + $deployment = $this->createDeployment($functionId, [ 'code' => $this->packageFunction('php'), 'activate' => false ]); + $deploymentIdInactive = $deployment['body']['$id']; + $this->assertEquals(202, $deployment['headers']['status-code']); $this->assertNotEmpty($deployment['body']['$id']); $this->assertEventually(function () use ($functionId, $deploymentId, $deploymentIdInactive) { $deployment = $this->getDeployment($functionId, $deploymentId); - $this->assertEquals('completed', $deployment['body']['status']); + $this->assertEquals('ready', $deployment['body']['status']); $deployment = $this->getDeployment($functionId, $deploymentIdInactive); - $this->assertEquals('completed', $deployment['body']['status']); + $this->assertEquals('ready', $deployment['body']['status']); }, 500000, 1000); $function = $this->getFunction($functionId); + $this->assertEquals(200, $function['headers']['status-code']); $this->assertEquals($deploymentId, $function['body']['deployment']); $this->assertNotEquals($deploymentIdInactive, $function['body']['deployment']); @@ -453,6 +483,7 @@ class FunctionsCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), []); + $this->assertEquals(204, $deployment['headers']['status-code']); return array_merge($data, ['deploymentId' => $deploymentId]); @@ -464,10 +495,12 @@ class FunctionsCustomServerTest extends Scope public function testCancelDeploymentBuild($data): void { $functionId = $data['functionId']; + $deployment = $this->createDeployment($functionId, [ 'code' => $this->packageFunction('php'), 'activate' => false ]); + $deploymentId = $deployment['body']['$id'] ?? ''; $this->assertEquals(202, $deployment['headers']['status-code']); @@ -477,6 +510,7 @@ class FunctionsCustomServerTest extends Scope $this->assertEventually(function () use ($functionId, $deploymentId) { $deployment = $this->getDeployment($functionId, $deploymentId); + $this->assertEquals(200, $deployment['headers']['status-code']); $this->assertEquals('building', $deployment['body']['status']); }, 100000, 250); @@ -486,6 +520,7 @@ class FunctionsCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); + $this->assertEquals(200, $cancel['headers']['status-code']); $this->assertEquals('canceled', $cancel['body']['status']); @@ -497,6 +532,7 @@ class FunctionsCustomServerTest extends Scope \sleep(30); $deployment = $this->getDeployment($functionId, $deploymentId); + $this->assertEquals(200, $deployment['headers']['status-code']); $this->assertEquals('canceled', $deployment['body']['status']); } @@ -554,8 +590,9 @@ class FunctionsCustomServerTest extends Scope $this->assertEventually(function () use ($functionId, $deploymentId, $deploymentSize) { $deployment = $this->getDeployment($functionId, $deploymentId); + $this->assertEquals(200, $deployment['headers']['status-code']); - $this->assertEquals('completed', $deployment['body']['status']); + $this->assertEquals('ready', $deployment['body']['status']); $this->assertEquals($deploymentSize, $deployment['body']['size']); $this->assertGreaterThan(1024 * 1024 * 10, $deployment['body']['buildSize']); // ~7MB video file + 10MB sample file }, 500000, 1000); @@ -609,6 +646,7 @@ class FunctionsCustomServerTest extends Scope $deployments = $this->listDeployments($functionId, [ 'search' => $data['functionId'] ]); + $this->assertEquals($deployments['headers']['status-code'], 200); $this->assertEquals(3, $deployments['body']['total']); $this->assertIsArray($deployments['body']['deployments']); @@ -621,6 +659,7 @@ class FunctionsCustomServerTest extends Scope Query::limit(1)->toString(), ], ]); + $this->assertEquals($deployments['headers']['status-code'], 200); $this->assertCount(1, $deployments['body']['deployments']); @@ -655,7 +694,6 @@ class FunctionsCustomServerTest extends Scope 'search' => 'Test' ]); - $this->assertEquals($deployments['headers']['status-code'], 200); $this->assertEquals(3, $deployments['body']['total']); $this->assertIsArray($deployments['body']['deployments']); @@ -753,6 +791,7 @@ class FunctionsCustomServerTest extends Scope Query::limit(1)->toString(), ] ); + $this->assertEquals(200, $deployments['headers']['status-code']); $this->assertGreaterThanOrEqual(1, $deployments['body']['total']); $this->assertNotEmpty($deployments['body']['deployments'][0]['$id']); @@ -775,7 +814,7 @@ class FunctionsCustomServerTest extends Scope $matchingDeployment = array_filter( $deployments['body']['deployments'], - fn ($deployment) => $deployment['$id'] === $deploymentId + fn($deployment) => $deployment['$id'] === $deploymentId ); $this->assertNotEmpty($matchingDeployment, "Deployment with ID {$deploymentId} not found"); @@ -797,6 +836,7 @@ class FunctionsCustomServerTest extends Scope * Test for SUCCESS */ $deployment = $this->getDeployment($data['functionId'], $data['deploymentId']); + $this->assertEquals(200, $deployment['headers']['status-code']); $this->assertGreaterThan(0, $deployment['body']['buildTime']); $this->assertNotEmpty($deployment['body']['status']); @@ -808,6 +848,7 @@ class FunctionsCustomServerTest extends Scope * Test for FAILURE */ $deployment = $this->getDeployment($data['functionId'], 'x'); + $this->assertEquals($deployment['headers']['status-code'], 404); return $data; @@ -824,7 +865,6 @@ class FunctionsCustomServerTest extends Scope $execution = $this->createExecution($data['functionId'], [ 'async' => false, ]); - $executionId = $execution['body']['$id'] ?? ''; $this->assertEquals(201, $execution['headers']['status-code']); $this->assertNotEmpty($execution['body']['$id']); @@ -845,10 +885,13 @@ class FunctionsCustomServerTest extends Scope $this->assertNotEmpty($execution['body']['logs']); $this->assertLessThan(10, $execution['body']['duration']); + $executionId = $execution['body']['$id'] ?? ''; + $execution = $this->createExecution($data['functionId'], [ 'async' => false, 'path' => '/?code=400' ]); + $this->assertEquals(201, $execution['headers']['status-code']); $this->assertEquals('completed', $execution['body']['status']); $this->assertEquals(400, $execution['body']['responseStatusCode']); @@ -859,6 +902,7 @@ class FunctionsCustomServerTest extends Scope ], $this->getHeaders()), []); $execution = $this->createExecution($data['functionId']); + $this->assertEquals(204, $execution['headers']['status-code']); return array_merge($data, ['executionId' => $executionId]); @@ -873,6 +917,7 @@ class FunctionsCustomServerTest extends Scope * Test for SUCCESS */ $executions = $this->listExecutions($data['functionId']); + $this->assertEquals($executions['headers']['status-code'], 200); $this->assertEquals($executions['body']['total'], 1); $this->assertIsArray($executions['body']['executions']); @@ -884,6 +929,7 @@ class FunctionsCustomServerTest extends Scope Query::limit(1)->toString(), ], ]); + $this->assertEquals(200, $executions['headers']['status-code']); $this->assertCount(1, $executions['body']['executions']); @@ -892,6 +938,7 @@ class FunctionsCustomServerTest extends Scope Query::offset(1)->toString(), ], ]); + $this->assertEquals(200, $executions['headers']['status-code']); $this->assertCount(0, $executions['body']['executions']); @@ -900,6 +947,7 @@ class FunctionsCustomServerTest extends Scope Query::equal('trigger', ['http'])->toString(), ], ]); + $this->assertEquals(200, $executions['headers']['status-code']); $this->assertCount(1, $executions['body']['executions']); @@ -909,6 +957,7 @@ class FunctionsCustomServerTest extends Scope $executions = $this->listExecutions($data['functionId'], [ 'search' => $data['executionId'], ]); + $this->assertEquals(200, $executions['headers']['status-code']); $this->assertEquals(1, $executions['body']['total']); $this->assertIsInt($executions['body']['total']); @@ -918,6 +967,7 @@ class FunctionsCustomServerTest extends Scope $executions = $this->listExecutions($data['functionId'], [ 'search' => $data['functionId'], ]); + $this->assertEquals(200, $executions['headers']['status-code']); $this->assertEquals(1, $executions['body']['total']); $this->assertIsInt($executions['body']['total']); @@ -938,6 +988,7 @@ class FunctionsCustomServerTest extends Scope $execution = $this->createExecution($data['functionId'], [ // Testing default value, should be 'async' => false ]); + $this->assertEquals(201, $execution['headers']['status-code']); $this->assertEquals('completed', $execution['body']['status']); $this->assertEquals(200, $execution['body']['responseStatusCode']); @@ -960,6 +1011,7 @@ class FunctionsCustomServerTest extends Scope * Test for SUCCESS */ $execution = $this->getExecution($data['functionId'], $data['executionId']); + $this->assertEquals($execution['headers']['status-code'], 200); $this->assertEquals($execution['body']['$id'], $data['executionId']); @@ -967,6 +1019,7 @@ class FunctionsCustomServerTest extends Scope * Test for FAILURE */ $function = $this->getExecution($data['functionId'], 'x'); + $this->assertEquals($function['headers']['status-code'], 404); return $data; @@ -1008,6 +1061,7 @@ class FunctionsCustomServerTest extends Scope ]); $executionId = $execution['body']['$id'] ?? ''; + $this->assertEquals(202, $execution['headers']['status-code']); $execution = $this->client->call(Client::METHOD_DELETE, '/functions/' . $data['functionId'] . '/executions/' . $executionId, array_merge([ @@ -1039,6 +1093,7 @@ class FunctionsCustomServerTest extends Scope ]); $executionId = $execution['body']['$id'] ?? ''; + $this->assertEquals(202, $execution['headers']['status-code']); $execution = $this->client->call(Client::METHOD_DELETE, '/functions/' . $data['functionId'] . '/executions/' . $executionId, [ @@ -1083,6 +1138,7 @@ class FunctionsCustomServerTest extends Scope $execution = $this->createExecution($data['functionId']); $output = json_decode($execution['body']['responseBody'], true); + $this->assertEquals(1, $output['APPWRITE_FUNCTION_CPUS']); $this->assertEquals(1024, $output['APPWRITE_FUNCTION_MEMORY']); @@ -1110,6 +1166,7 @@ class FunctionsCustomServerTest extends Scope $execution = $this->createExecution($data['functionId']); $output = json_decode($execution['body']['responseBody'], true); + $this->assertEquals(1, $output['APPWRITE_FUNCTION_CPUS']); $this->assertEquals(512, $output['APPWRITE_FUNCTION_MEMORY']); @@ -1130,6 +1187,7 @@ class FunctionsCustomServerTest extends Scope 'entrypoint' => 'index.php', 'specification' => 's-2vcpu-512mb', // Invalid specification ]); + $this->assertEquals(400, $function['headers']['status-code']); $this->assertStringStartsWith('Invalid `specification` param: Specification must be one of:', $function['body']['message']); @@ -1148,10 +1206,12 @@ class FunctionsCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); + $this->assertEquals(204, $deployment['headers']['status-code']); $this->assertEmpty($deployment['body']); $deployment = $this->getDeployment($data['functionId'], $data['deploymentId']); + $this->assertEquals(404, $deployment['headers']['status-code']); return $data; @@ -1166,10 +1226,12 @@ class FunctionsCustomServerTest extends Scope * Test for SUCCESS */ $function = $this->deleteFunction($data['functionId']); + $this->assertEquals(204, $function['headers']['status-code']); $this->assertEmpty($function['body']); $function = $this->getFunction($data['functionId']); + $this->assertEquals(404, $function['headers']['status-code']); return $data; @@ -1193,9 +1255,11 @@ class FunctionsCustomServerTest extends Scope $execution = $this->createExecution($functionId, [ 'async' => true ]); - $executionId = $execution['body']['$id'] ?? ''; + $this->assertEquals(202, $execution['headers']['status-code']); + $executionId = $execution['body']['$id'] ?? ''; + \sleep(5); // Wait for the function to timeout $this->assertEventually(function () use ($functionId, $executionId) { @@ -1254,6 +1318,7 @@ class FunctionsCustomServerTest extends Scope 'key' => 'CUSTOM_VARIABLE', 'value' => 'variable' ]); + $this->assertEquals(201, $variable['headers']['status-code']); $deploymentId = $this->setupDeployment($functionId, [ @@ -1266,7 +1331,7 @@ class FunctionsCustomServerTest extends Scope 'body' => 'foobar', 'async' => false ]); - $executionId = $execution['body']['$id'] ?? ''; + $output = json_decode($execution['body']['responseBody'], true); $this->assertEquals(201, $execution['headers']['status-code']); $this->assertEquals('completed', $execution['body']['status']); @@ -1286,7 +1351,10 @@ class FunctionsCustomServerTest extends Scope $this->assertStringContainsString('Amazing Function Log', $execution['body']['logs']); $this->assertEmpty($execution['body']['errors']); + $executionId = $execution['body']['$id'] ?? ''; + $executions = $this->listExecutions($functionId); + $this->assertEquals($executions['headers']['status-code'], 200); $this->assertEquals($executions['body']['total'], 1); $this->assertIsArray($executions['body']['executions']); @@ -1374,6 +1442,7 @@ class FunctionsCustomServerTest extends Scope ], false); $executionBody = json_decode($execution['body'], true); + $this->assertEquals(201, $execution['headers']['status-code']); $this->assertEquals(\md5($bytes), $executionBody['responseBody']); $this->assertStringStartsWith('application/json', $execution['headers']['content-type']); @@ -1390,6 +1459,7 @@ class FunctionsCustomServerTest extends Scope ], false); $executionBody = json_decode($execution['body'], true); + $this->assertNotEquals(\md5($bytes), $executionBody['responseBody']); $this->cleanupFunction($functionId); @@ -1427,6 +1497,7 @@ class FunctionsCustomServerTest extends Scope 'body' => 'foobar', 'async' => false ]); + $output = json_decode($execution['body']['responseBody'], true); $this->assertEquals(201, $execution['headers']['status-code']); $this->assertEquals('completed', $execution['body']['status']); @@ -1485,12 +1556,16 @@ class FunctionsCustomServerTest extends Scope 'userId' => 'unique()', 'name' => 'Event User' ]); - $userId = $user['body']['$id'] ?? ''; + $this->assertEquals(201, $user['headers']['status-code']); + $userId = $user['body']['$id'] ?? ''; + $this->assertEventually(function () use ($functionId, $userId) { $executions = $this->listExecutions($functionId); + $lastExecution = $executions['body']['executions'][0]; + $this->assertEquals('completed', $lastExecution['status']); $this->assertEquals(204, $lastExecution['responseStatusCode']); $this->assertStringContainsString($userId, $lastExecution['logs']); @@ -1548,10 +1623,12 @@ class FunctionsCustomServerTest extends Scope $execution = $this->createExecution($functionId, [ 'async' => true, ]); - $executionId = $execution['body']['$id'] ?? ''; + $this->assertEquals(202, $execution['headers']['status-code']); $this->assertNotEmpty($execution['body']['$id']); + $executionId = $execution['body']['$id'] ?? ''; + $this->assertEventually(function () use ($functionId, $executionId) { $execution = $this->getExecution($functionId, $executionId); @@ -1646,7 +1723,7 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals(200, $response['headers']['status-code']); $this->assertEquals($cookie, $response['body']); - // Await Aggregation + // Wait for usage to be aggregated sleep(System::getEnv('_APP_USAGE_AGGREGATION_INTERVAL', 30)); $this->assertEventually(function () use ($functionId) { From 906ef59d95b221af625d508d54551e5c8d88fff7 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Thu, 19 Sep 2024 21:36:19 +0100 Subject: [PATCH 11/33] chore: fmt --- tests/e2e/Services/Functions/FunctionsCustomServerTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php index 47e80efde9..82f80a8cd8 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php @@ -814,7 +814,7 @@ class FunctionsCustomServerTest extends Scope $matchingDeployment = array_filter( $deployments['body']['deployments'], - fn($deployment) => $deployment['$id'] === $deploymentId + fn ($deployment) => $deployment['$id'] === $deploymentId ); $this->assertNotEmpty($matchingDeployment, "Deployment with ID {$deploymentId} not found"); From 19ea682a52e462418223654dc4e8f1fe3f8b2f1b Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Thu, 19 Sep 2024 21:54:25 +0100 Subject: [PATCH 12/33] chore: timeouts --- .../Services/Functions/FunctionsCustomClientTest.php | 2 +- .../Services/Functions/FunctionsCustomServerTest.php | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php index dd950ca655..53713cfd40 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php @@ -244,7 +244,7 @@ class FunctionsCustomClientTest extends Scope $execution = $this->getExecution($functionId, $executionId); $this->assertEquals('completed', $execution['body']['status']); $this->assertEquals(200, $execution['body']['responseStatusCode']); - }, 10000, 250); + }, 10000, 500); return [ 'functionId' => $functionId diff --git a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php index 82f80a8cd8..b895e4c938 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php @@ -373,12 +373,14 @@ class FunctionsCustomServerTest extends Scope $this->assertEventually(function () use ($functionId, $deploymentId) { $deployment = $this->getDeployment($functionId, $deploymentId); + $this->assertEquals(200, $deployment['headers']['status-code']); $this->assertEquals('ready', $deployment['body']['status']); $this->assertEquals('cli', $deployment['body']['type']); }, 500000, 1000); $function = $this->getFunction($functionId); + $this->assertEquals(200, $function['headers']['status-code']); $this->assertEquals($deploymentId, $function['body']['deployment']); @@ -428,7 +430,7 @@ class FunctionsCustomServerTest extends Scope $this->assertEmpty($execution['body']['responseBody']); $this->assertEmpty($execution['body']['errors']); $this->assertStringContainsString("Total users: " . $totalUsers, $execution['body']['logs']); - }, 100000, 250); + }, 10000, 500); $function = $this->deleteFunction($functionId); } @@ -1273,7 +1275,7 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals('', $execution['body']['responseBody']); $this->assertEquals('', $execution['body']['logs']); $this->assertStringContainsString('timed out', $execution['body']['errors']); - }, 5000, 250); + }, 10000, 500); $this->cleanupFunction($functionId); } @@ -1638,7 +1640,7 @@ class FunctionsCustomServerTest extends Scope $this->assertGreaterThan(0, $execution['body']['duration']); $this->assertNotEmpty($execution['body']['responseBody']); $this->assertStringContainsString("total", $execution['body']['responseBody']); - }, 5000, 250); + }, 10000, 500); $this->cleanupFunction($functionId); } @@ -1905,7 +1907,7 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals('completed', $execution['body']['status']); $this->assertEmpty($execution['body']['logs']); $this->assertEmpty($execution['body']['errors']); - }, 5000, 250); + }, 10000, 500); // Domain Executions test $rules = $this->client->call(Client::METHOD_GET, '/proxy/rules', array_merge([ From 92ce8d711d923a1b4398d5d1d6cc7ad847dfc8ff Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Thu, 19 Sep 2024 22:10:04 +0100 Subject: [PATCH 13/33] feat: split scheduled tests --- .../Functions/FunctionsConsoleClientTest.php | 12 +- .../Functions/FunctionsCustomClientTest.php | 115 ---------- .../Functions/FunctionsCustomServerTest.php | 36 +--- .../Functions/FunctionsScheduleTest.php | 203 ++++++++++++++++++ 4 files changed, 216 insertions(+), 150 deletions(-) create mode 100644 tests/e2e/Services/Functions/FunctionsScheduleTest.php diff --git a/tests/e2e/Services/Functions/FunctionsConsoleClientTest.php b/tests/e2e/Services/Functions/FunctionsConsoleClientTest.php index cbfca09564..3a02cbcba2 100644 --- a/tests/e2e/Services/Functions/FunctionsConsoleClientTest.php +++ b/tests/e2e/Services/Functions/FunctionsConsoleClientTest.php @@ -30,9 +30,11 @@ class FunctionsConsoleClientTest extends Scope 'schedule' => '0 0 1 1 *', 'timeout' => 10, ]); - $functionId = $function['body']['$id']; + $this->assertEquals(201, $function['headers']['status-code']); + $functionId = $function['body']['$id']; + $function2 = $this->createFunction([ 'functionId' => ID::unique(), 'name' => 'Test Failure', @@ -40,6 +42,7 @@ class FunctionsConsoleClientTest extends Scope 'runtime' => 'php-8.0', 'entrypoint' => 'index.php', ]); + $this->assertEquals(400, $function2['headers']['status-code']); return [ @@ -110,9 +113,11 @@ class FunctionsConsoleClientTest extends Scope 'value' => 'TESTINGVALUE' ] ); - $variableId = $variable['body']['$id']; + $this->assertEquals(201, $variable['headers']['status-code']); + $variableId = $variable['body']['$id']; + /** * Test for FAILURE */ @@ -124,6 +129,7 @@ class FunctionsConsoleClientTest extends Scope 'value' => 'ANOTHERTESTINGVALUE' ] ); + $this->assertEquals(409, $variable['headers']['status-code']); // Test for invalid key @@ -134,6 +140,7 @@ class FunctionsConsoleClientTest extends Scope 'value' => 'TESTINGVALUE' ] ); + $this->assertEquals(400, $variable['headers']['status-code']); // Test for invalid value @@ -144,6 +151,7 @@ class FunctionsConsoleClientTest extends Scope 'value' => str_repeat("#", 8193), ] ); + $this->assertEquals(400, $variable['headers']['status-code']); return array_merge( diff --git a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php index 53713cfd40..3447ee0547 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php @@ -48,7 +48,6 @@ class FunctionsCustomClientTest extends Scope 'users.*.create', 'users.*.delete', ], - 'schedule' => '* * * * *', // Execute every 60 seconds 'timeout' => 10, ]); $this->setupDeployment($functionId, [ @@ -72,124 +71,10 @@ class FunctionsCustomClientTest extends Scope ]); $this->assertEquals(202, $execution['headers']['status-code']); - // Wait for scheduled execution - \sleep(60); - - $this->assertEventually(function () use ($functionId) { - $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['key'], - ]); - $this->assertEquals(200, $executions['headers']['status-code']); - $this->assertCount(2, $executions['body']['executions']); - - $asyncExecution = $executions['body']['executions'][1]; - $this->assertEquals('schedule', $asyncExecution['trigger']); - $this->assertEquals('completed', $asyncExecution['status']); - $this->assertEquals(200, $asyncExecution['responseStatusCode']); - $this->assertEquals('', $asyncExecution['responseBody']); - $this->assertNotEmpty($asyncExecution['logs']); - $this->assertNotEmpty($asyncExecution['errors']); - $this->assertGreaterThan(0, $asyncExecution['duration']); - }, 10000, 500); - $this->cleanupFunction($functionId); } - public function testCreateScheduledExecution(): void - { - /** - * Test for SUCCESS - */ - $functionId = $this->setupFunction([ - 'functionId' => ID::unique(), - 'name' => 'Test', - 'execute' => [Role::user($this->getUser()['$id'])->toString()], - 'runtime' => 'php-8.0', - 'entrypoint' => 'index.php', - 'timeout' => 10, - 'logging' => true, - ]); - $this->setupDeployment($functionId, [ - 'entrypoint' => 'index.php', - 'code' => $this->packageFunction('php'), - 'activate' => true - ]); - // Schedule execution for the future - \date_default_timezone_set('UTC'); - $futureTime = (new \DateTime())->add(new \DateInterval('PT2M')); // 2 minute in the future - $futureTime->setTime($futureTime->format('H'), $futureTime->format('i'), 0, 0); - - $execution = $this->createExecution($functionId, [ - 'async' => true, - 'scheduledAt' => $futureTime->format(\DateTime::ATOM), - 'path' => '/custom-path', - 'method' => 'PATCH', - 'body' => 'custom-body', - 'headers' => [ - 'x-custom-header' => 'custom-value' - ] - ]); - $executionId = $execution['body']['$id']; - - $this->assertEquals(202, $execution['headers']['status-code']); - $this->assertEquals('scheduled', $execution['body']['status']); - $this->assertEquals('PATCH', $execution['body']['requestMethod']); - $this->assertEquals('/custom-path', $execution['body']['requestPath']); - $this->assertCount(0, $execution['body']['requestHeaders']); - - \sleep(120); - - $this->assertEventually(function () use ($functionId, $executionId) { - $execution = $this->getExecution($functionId, $executionId); - - $this->assertEquals(200, $execution['headers']['status-code']); - $this->assertEquals(200, $execution['body']['responseStatusCode']); - $this->assertEquals('completed', $execution['body']['status']); - $this->assertEquals('/custom-path', $execution['body']['requestPath']); - $this->assertEquals('PATCH', $execution['body']['requestMethod']); - $this->assertStringContainsString('body-is-custom-body', $execution['body']['logs']); - $this->assertStringContainsString('custom-header-is-custom-value', $execution['body']['logs']); - $this->assertStringContainsString('method-is-patch', $execution['body']['logs']); - $this->assertStringContainsString('path-is-/custom-path', $execution['body']['logs']); - $this->assertStringContainsString('user-is-' . $this->getUser()['$id'], $execution['body']['logs']); - $this->assertStringContainsString('jwt-is-valid', $execution['body']['logs']); - $this->assertGreaterThan(0, $execution['body']['duration']); - }, 10000, 500); - - /* Test for FAILURE */ - // Schedule synchronous execution - $execution = $this->createExecution($functionId, [ - 'async' => false, - 'scheduledAt' => $futureTime->format(\DateTime::ATOM), - ]); - $this->assertEquals(400, $execution['headers']['status-code']); - - // Execution with seconds precision - $execution = $this->createExecution($functionId, [ - 'async' => true, - 'scheduledAt' => (new \DateTime("2100-12-08 16:12:02"))->format(\DateTime::ATOM) - ]); - $this->assertEquals(400, $execution['headers']['status-code']); - - // Execution with milliseconds precision - $execution = $this->createExecution($functionId, [ - 'async' => true, - 'scheduledAt' => (new \DateTime("2100-12-08 16:12:02.255"))->format(\DateTime::ATOM) - ]); - $this->assertEquals(400, $execution['headers']['status-code']); - - // Execution too soon - $execution = $this->createExecution($functionId, [ - 'async' => true, - 'scheduledAt' => (new \DateTime())->add(new \DateInterval('PT1S'))->format(\DateTime::ATOM) - ]); - $this->assertEquals(400, $execution['headers']['status-code']); - - $this->cleanupFunction($functionId, $executionId); - } public function testCreateCustomExecution(): array { diff --git a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php index b895e4c938..71dce2258c 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php @@ -35,7 +35,6 @@ class FunctionsCustomServerTest extends Scope 'buckets.*.create', 'buckets.*.delete', ], - 'schedule' => '0 0 1 1 *', 'timeout' => 10, ]); @@ -53,7 +52,7 @@ class FunctionsCustomServerTest extends Scope 'buckets.*.create', 'buckets.*.delete', ], $function['body']['events']); - $this->assertEquals('0 0 1 1 *', $function['body']['schedule']); + $this->assertEmpty($function['body']['schedule']); $this->assertEquals(10, $function['body']['timeout']); $variable = $this->createVariable($functionId, [ @@ -156,7 +155,7 @@ class FunctionsCustomServerTest extends Scope /** * Test pagination */ - $functionId = $this->setupFunction([ + $this->setupFunction([ 'functionId' => ID::unique(), 'name' => 'Test 2', 'runtime' => 'php-8.0', @@ -165,7 +164,6 @@ class FunctionsCustomServerTest extends Scope 'buckets.*.create', 'buckets.*.delete', ], - 'schedule' => '0 0 1 1 *', 'timeout' => 10, ]); @@ -816,7 +814,7 @@ class FunctionsCustomServerTest extends Scope $matchingDeployment = array_filter( $deployments['body']['deployments'], - fn ($deployment) => $deployment['$id'] === $deploymentId + fn($deployment) => $deployment['$id'] === $deploymentId ); $this->assertNotEmpty($matchingDeployment, "Deployment with ID {$deploymentId} not found"); @@ -1078,35 +1076,7 @@ class FunctionsCustomServerTest extends Scope return $data; } - /** - * @depends testGetExecution - */ - public function testDeleteScheduledExecution($data): array - { - $futureTime = (new \DateTime())->add(new \DateInterval('PT10H')); - $futureTime->setTime($futureTime->format('H'), $futureTime->format('i'), 0, 0); - $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $data['functionId'] . '/executions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'async' => true, - 'scheduledAt' => $futureTime->format('Y-m-d H:i:s'), - ]); - - $executionId = $execution['body']['$id'] ?? ''; - - $this->assertEquals(202, $execution['headers']['status-code']); - - $execution = $this->client->call(Client::METHOD_DELETE, '/functions/' . $data['functionId'] . '/executions/' . $executionId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ]); - - $this->assertEquals(204, $execution['headers']['status-code']); - - return $data; - } /** * @depends testGetExecution diff --git a/tests/e2e/Services/Functions/FunctionsScheduleTest.php b/tests/e2e/Services/Functions/FunctionsScheduleTest.php new file mode 100644 index 0000000000..bab9a2a684 --- /dev/null +++ b/tests/e2e/Services/Functions/FunctionsScheduleTest.php @@ -0,0 +1,203 @@ +setupFunction([ + 'functionId' => ID::unique(), + 'name' => 'Test', + 'execute' => [Role::user($this->getUser()['$id'])->toString()], + 'runtime' => 'php-8.0', + 'entrypoint' => 'index.php', + 'events' => [ + 'users.*.create', + 'users.*.delete', + ], + 'schedule' => '* * * * *', // Execute every 60 seconds + 'timeout' => 10, + ]); + + $this->setupDeployment($functionId, [ + 'entrypoint' => 'index.php', + 'code' => $this->packageFunction('php'), + 'activate' => true + ]); + + // Wait for scheduled execution + \sleep(60); + + $this->assertEventually(function () use ($functionId) { + $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['key'], + ]); + + $this->assertEquals(200, $executions['headers']['status-code']); + $this->assertCount(1, $executions['body']['executions']); + + $asyncExecution = $executions['body']['executions'][0]; + + $this->assertEquals('schedule', $asyncExecution['trigger']); + $this->assertEquals('completed', $asyncExecution['status']); + $this->assertEquals(200, $asyncExecution['responseStatusCode']); + $this->assertEquals('', $asyncExecution['responseBody']); + $this->assertNotEmpty($asyncExecution['logs']); + $this->assertNotEmpty($asyncExecution['errors']); + $this->assertGreaterThan(0, $asyncExecution['duration']); + }, 10000, 500); + + $this->cleanupFunction($functionId); + } + + public function testCreateScheduledAtExecution(): void + { + /** + * Test for SUCCESS + */ + $functionId = $this->setupFunction([ + 'functionId' => ID::unique(), + 'name' => 'Test', + 'execute' => [Role::user($this->getUser()['$id'])->toString()], + 'runtime' => 'php-8.0', + 'entrypoint' => 'index.php', + 'timeout' => 10, + 'logging' => true, + ]); + $this->setupDeployment($functionId, [ + 'entrypoint' => 'index.php', + 'code' => $this->packageFunction('php'), + 'activate' => true + ]); + + // Schedule execution for the future + \date_default_timezone_set('UTC'); + $futureTime = (new \DateTime())->add(new \DateInterval('PT2M')); // 2 minute in the future + $futureTime->setTime($futureTime->format('H'), $futureTime->format('i'), 0, 0); + + $execution = $this->createExecution($functionId, [ + 'async' => true, + 'scheduledAt' => $futureTime->format(\DateTime::ATOM), + 'path' => '/custom-path', + 'method' => 'PATCH', + 'body' => 'custom-body', + 'headers' => [ + 'x-custom-header' => 'custom-value' + ] + ]); + $executionId = $execution['body']['$id']; + + $this->assertEquals(202, $execution['headers']['status-code']); + $this->assertEquals('scheduled', $execution['body']['status']); + $this->assertEquals('PATCH', $execution['body']['requestMethod']); + $this->assertEquals('/custom-path', $execution['body']['requestPath']); + $this->assertCount(0, $execution['body']['requestHeaders']); + + \sleep(120); + + $this->assertEventually(function () use ($functionId, $executionId) { + $execution = $this->getExecution($functionId, $executionId); + + $this->assertEquals(200, $execution['headers']['status-code']); + $this->assertEquals(200, $execution['body']['responseStatusCode']); + $this->assertEquals('completed', $execution['body']['status']); + $this->assertEquals('/custom-path', $execution['body']['requestPath']); + $this->assertEquals('PATCH', $execution['body']['requestMethod']); + $this->assertStringContainsString('body-is-custom-body', $execution['body']['logs']); + $this->assertStringContainsString('custom-header-is-custom-value', $execution['body']['logs']); + $this->assertStringContainsString('method-is-patch', $execution['body']['logs']); + $this->assertStringContainsString('path-is-/custom-path', $execution['body']['logs']); + $this->assertStringContainsString('user-is-' . $this->getUser()['$id'], $execution['body']['logs']); + $this->assertStringContainsString('jwt-is-valid', $execution['body']['logs']); + $this->assertGreaterThan(0, $execution['body']['duration']); + }, 10000, 500); + + /* Test for FAILURE */ + // Schedule synchronous execution + $execution = $this->createExecution($functionId, [ + 'async' => false, + 'scheduledAt' => $futureTime->format(\DateTime::ATOM), + ]); + $this->assertEquals(400, $execution['headers']['status-code']); + + // Execution with seconds precision + $execution = $this->createExecution($functionId, [ + 'async' => true, + 'scheduledAt' => (new \DateTime("2100-12-08 16:12:02"))->format(\DateTime::ATOM) + ]); + $this->assertEquals(400, $execution['headers']['status-code']); + + // Execution with milliseconds precision + $execution = $this->createExecution($functionId, [ + 'async' => true, + 'scheduledAt' => (new \DateTime("2100-12-08 16:12:02.255"))->format(\DateTime::ATOM) + ]); + $this->assertEquals(400, $execution['headers']['status-code']); + + // Execution too soon + $execution = $this->createExecution($functionId, [ + 'async' => true, + 'scheduledAt' => (new \DateTime())->add(new \DateInterval('PT1S'))->format(\DateTime::ATOM) + ]); + $this->assertEquals(400, $execution['headers']['status-code']); + + $this->cleanupFunction($functionId, $executionId); + } + + public function testDeleteScheduledExecution() + { + $functionId = $this->setupFunction([ + 'functionId' => ID::unique(), + 'name' => 'Test', + 'execute' => [Role::user($this->getUser()['$id'])->toString()], + 'runtime' => 'php-8.0', + 'entrypoint' => 'index.php', + 'timeout' => 10, + 'logging' => true, + ]); + + $this->setupDeployment($functionId, [ + 'entrypoint' => 'index.php', + 'code' => $this->packageFunction('php'), + 'activate' => true + ]); + + $futureTime = (new \DateTime())->add(new \DateInterval('PT10H')); + $futureTime->setTime($futureTime->format('H'), $futureTime->format('i'), 0, 0); + + $execution = $this->createExecution($functionId, [ + 'async' => true, + 'scheduledAt' => $futureTime->format('Y-m-d H:i:s'), + ]); + + $this->assertEquals(202, $execution['headers']['status-code']); + + $executionId = $execution['body']['$id'] ?? ''; + + $execution = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId . '/executions/' . $executionId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(204, $execution['headers']['status-code']); + + $this->cleanupFunction($functionId); + } +} From f04e47b3d455b99ed6d36d04baa6c61143e05313 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Thu, 19 Sep 2024 22:11:44 +0100 Subject: [PATCH 14/33] chore: fmt --- tests/e2e/Services/Functions/FunctionsCustomServerTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php index 71dce2258c..5db051da2b 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php @@ -814,7 +814,7 @@ class FunctionsCustomServerTest extends Scope $matchingDeployment = array_filter( $deployments['body']['deployments'], - fn($deployment) => $deployment['$id'] === $deploymentId + fn ($deployment) => $deployment['$id'] === $deploymentId ); $this->assertNotEmpty($matchingDeployment, "Deployment with ID {$deploymentId} not found"); From 321227db15a5fc9dbbba5ecc667b6dbcd7cbd05c Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Fri, 20 Sep 2024 09:15:05 +0100 Subject: [PATCH 15/33] fix: seperate tests --- .github/workflows/tests.yml | 3 ++- tests/e2e/Services/Functions/FunctionsBase.php | 18 ++++++++---------- .../Functions/FunctionsCustomServerTest.php | 11 ++++++----- .../FunctionsScheduleTest.php | 6 +++--- 4 files changed, 19 insertions(+), 19 deletions(-) rename tests/e2e/Services/{Functions => FunctionsSchedule}/FunctionsScheduleTest.php (98%) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 633bd46ea4..5b82cba594 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -113,6 +113,7 @@ jobs: Console, Databases, Functions, + FunctionsSchedule, GraphQL, Health, Locale, @@ -141,7 +142,7 @@ jobs: run: | docker load --input /tmp/${{ env.IMAGE }}.tar docker compose up -d - sleep 25 + sleep 10 - name: Run ${{matrix.service}} Tests run: docker compose exec -T appwrite test /usr/src/code/tests/e2e/Services/${{matrix.service}} --debug diff --git a/tests/e2e/Services/Functions/FunctionsBase.php b/tests/e2e/Services/Functions/FunctionsBase.php index 5ed87b4895..8b19c65705 100644 --- a/tests/e2e/Services/Functions/FunctionsBase.php +++ b/tests/e2e/Services/Functions/FunctionsBase.php @@ -101,7 +101,7 @@ trait FunctionsBase return $deployment; } - protected function getExecution($functionId, $executionId) + protected function getExecution(string $functionId, $executionId): mixed { $execution = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions/' . $executionId, array_merge([ 'content-type' => 'application/json', @@ -111,7 +111,7 @@ trait FunctionsBase return $execution; } - protected function listFunctions($params = []) + protected function listFunctions(mixed $params = []): mixed { $functions = $this->client->call(Client::METHOD_GET, '/functions', array_merge([ 'content-type' => 'application/json', @@ -121,7 +121,7 @@ trait FunctionsBase return $functions; } - protected function listDeployments($functionId, $params = []) + protected function listDeployments(string $functionId, $params = []): mixed { $deployments = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/deployments', array_merge([ 'content-type' => 'application/json', @@ -131,7 +131,7 @@ trait FunctionsBase return $deployments; } - protected function listExecutions($functionId, $params = []) + protected function listExecutions(string $functionId, mixed $params = []): mixed { $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', array_merge([ 'content-type' => 'application/json', @@ -157,10 +157,8 @@ trait FunctionsBase return new CURLFile($tarPath, 'application/x-gzip', \basename($tarPath)); } - protected function createDeployment( - string $functionId, - mixed $params - ) { + protected function createDeployment(string $functionId, mixed $params = []): mixed + { $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', array_merge([ 'content-type' => 'multipart/form-data', 'x-appwrite-project' => $this->getProject()['$id'], @@ -189,7 +187,7 @@ trait FunctionsBase return $template; } - protected function createExecution(string $functionId, mixed $params = []) + protected function createExecution(string $functionId, mixed $params = []): mixed { $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([ 'content-type' => 'application/json', @@ -199,7 +197,7 @@ trait FunctionsBase return $execution; } - protected function deleteFunction($functionId) + protected function deleteFunction(string $functionId): mixed { $function = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, [ 'content-type' => 'application/json', diff --git a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php index 5db051da2b..b57cd3d0b8 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php @@ -144,7 +144,7 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals($functions['body']['functions'][0]['$id'], $data['functionId']); // Test search runtime - $functions = $this->listExecutions([ + $functions = $this->listFunctions([ 'search' => 'php-8.0' ]); @@ -490,7 +490,7 @@ class FunctionsCustomServerTest extends Scope } /** - * @depends testUpdate + * @depends testUpdateFunction */ public function testCancelDeploymentBuild($data): void { @@ -538,7 +538,7 @@ class FunctionsCustomServerTest extends Scope } /** - * @depends testUpdate + * @depends testUpdateFunction */ public function testCreateDeploymentLarge($data): array { @@ -814,7 +814,7 @@ class FunctionsCustomServerTest extends Scope $matchingDeployment = array_filter( $deployments['body']['deployments'], - fn ($deployment) => $deployment['$id'] === $deploymentId + fn($deployment) => $deployment['$id'] === $deploymentId ); $this->assertNotEmpty($matchingDeployment, "Deployment with ID {$deploymentId} not found"); @@ -1273,7 +1273,7 @@ class FunctionsCustomServerTest extends Scope * @param string $entrypoint * * @dataProvider provideCustomExecutions - * @depends testTimeout + * @depends testExecutionTimeout */ public function testCreateCustomExecution(string $folder, string $name, string $entrypoint, string $runtimeName, string $runtimeVersion) { @@ -1743,6 +1743,7 @@ class FunctionsCustomServerTest extends Scope $this->setupDeployment($functionId, [ 'entrypoint' => 'index.php', 'code' => $this->packageFunction('php-binary-response'), + 'activate' => true ]); $proxyClient = new Client(); diff --git a/tests/e2e/Services/Functions/FunctionsScheduleTest.php b/tests/e2e/Services/FunctionsSchedule/FunctionsScheduleTest.php similarity index 98% rename from tests/e2e/Services/Functions/FunctionsScheduleTest.php rename to tests/e2e/Services/FunctionsSchedule/FunctionsScheduleTest.php index bab9a2a684..f39efb42b5 100644 --- a/tests/e2e/Services/Functions/FunctionsScheduleTest.php +++ b/tests/e2e/Services/FunctionsSchedule/FunctionsScheduleTest.php @@ -6,14 +6,14 @@ use Appwrite\ID; use Tests\E2E\Client; use Tests\E2E\Scopes\ProjectCustom; use Tests\E2E\Scopes\Scope; -use Tests\E2E\Scopes\SideServer; +use Tests\E2E\Scopes\SideClient; use Utopia\Database\Helpers\Role; class FunctionsScheduleTest extends Scope { use FunctionsBase; use ProjectCustom; - use SideServer; + use SideClient; public function testCreateScheduledExecution() { @@ -47,7 +47,7 @@ class FunctionsScheduleTest extends Scope $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', [ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['key'], + 'x-appwrite-key' => $this->getProject()['apiKey'], ]); $this->assertEquals(200, $executions['headers']['status-code']); From 72ddef855ed6452c7ffd9dd5d82ec43908ef01f0 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Fri, 20 Sep 2024 09:16:18 +0100 Subject: [PATCH 16/33] chore: fmt --- tests/e2e/Services/Functions/FunctionsCustomServerTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php index b57cd3d0b8..e9297a109f 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php @@ -814,7 +814,7 @@ class FunctionsCustomServerTest extends Scope $matchingDeployment = array_filter( $deployments['body']['deployments'], - fn($deployment) => $deployment['$id'] === $deploymentId + fn ($deployment) => $deployment['$id'] === $deploymentId ); $this->assertNotEmpty($matchingDeployment, "Deployment with ID {$deploymentId} not found"); From e4ad2ed566e349c1bfa849074bcbebeafaf8446f Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Fri, 20 Sep 2024 09:33:26 +0100 Subject: [PATCH 17/33] fix: realtime tests --- .../FunctionsScheduleTest.php | 33 ++++++++++++------- tests/e2e/Services/Realtime/RealtimeBase.php | 21 ++++++++++++ .../Realtime/RealtimeCustomClientTest.php | 9 +---- 3 files changed, 44 insertions(+), 19 deletions(-) diff --git a/tests/e2e/Services/FunctionsSchedule/FunctionsScheduleTest.php b/tests/e2e/Services/FunctionsSchedule/FunctionsScheduleTest.php index f39efb42b5..7e36662e9e 100644 --- a/tests/e2e/Services/FunctionsSchedule/FunctionsScheduleTest.php +++ b/tests/e2e/Services/FunctionsSchedule/FunctionsScheduleTest.php @@ -6,14 +6,14 @@ use Appwrite\ID; use Tests\E2E\Client; use Tests\E2E\Scopes\ProjectCustom; use Tests\E2E\Scopes\Scope; -use Tests\E2E\Scopes\SideClient; +use Tests\E2E\Scopes\SideServer; use Utopia\Database\Helpers\Role; class FunctionsScheduleTest extends Scope { use FunctionsBase; use ProjectCustom; - use SideClient; + use SideServer; public function testCreateScheduledExecution() { @@ -92,16 +92,27 @@ class FunctionsScheduleTest extends Scope $futureTime = (new \DateTime())->add(new \DateInterval('PT2M')); // 2 minute in the future $futureTime->setTime($futureTime->format('H'), $futureTime->format('i'), 0, 0); - $execution = $this->createExecution($functionId, [ - 'async' => true, - 'scheduledAt' => $futureTime->format(\DateTime::ATOM), - 'path' => '/custom-path', - 'method' => 'PATCH', - 'body' => 'custom-body', - 'headers' => [ - 'x-custom-header' => 'custom-value' + + $execution = $this->client->call( + Client::METHOD_POST, + '/functions/' . $functionId . '/executions', + [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'origin' => 'http://localhost', + 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $this->getUser()['session'], + ], + [ + 'async' => true, + 'scheduledAt' => $futureTime->format(\DateTime::ATOM), + 'path' => '/custom-path', + 'method' => 'PATCH', + 'body' => 'custom-body', + 'headers' => [ + 'x-custom-header' => 'custom-value' + ] ] - ]); + ); $executionId = $execution['body']['$id']; $this->assertEquals(202, $execution['headers']['status-code']); diff --git a/tests/e2e/Services/Realtime/RealtimeBase.php b/tests/e2e/Services/Realtime/RealtimeBase.php index 30c411ba93..f0cd0e7c3b 100644 --- a/tests/e2e/Services/Realtime/RealtimeBase.php +++ b/tests/e2e/Services/Realtime/RealtimeBase.php @@ -2,6 +2,8 @@ namespace Tests\E2E\Services\Realtime; +use CURLFile; +use Utopia\CLI\Console; use WebSocket\Client as WebSocketClient; use WebSocket\ConnectionException; @@ -71,4 +73,23 @@ trait RealtimeBase $this->expectException(ConnectionException::class); // Check if server disconnnected client $client->close(); } + + // Function-related methods + protected string $output = ''; + + protected function packageFunction(string $function = 'php'): CURLFile + { + $folderPath = realpath(__DIR__ . '/../../../resources/functions') . "/$function"; + $tarPath = "$folderPath/code.tar.gz"; + + if (!file_exists($tarPath)) { + Console::execute("cd $folderPath && tar --exclude code.tar.gz -czf code.tar.gz .", '', $this->output); + } + + if (filesize($tarPath) > 1024 * 1024 * 5) { + throw new \Exception('Code package is too large. Use the chunked upload method instead.'); + } + + return new CURLFile($tarPath, 'application/x-gzip', \basename($tarPath)); + } } diff --git a/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php b/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php index 86aaee2ab0..4c23bb2eb9 100644 --- a/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php +++ b/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php @@ -7,7 +7,6 @@ use Tests\E2E\Client; use Tests\E2E\Scopes\ProjectCustom; use Tests\E2E\Scopes\Scope; use Tests\E2E\Scopes\SideClient; -use Utopia\CLI\Console; use Utopia\Database\Helpers\ID; use Utopia\Database\Helpers\Permission; use Utopia\Database\Helpers\Role; @@ -1271,19 +1270,13 @@ class RealtimeCustomClientTest extends Scope $this->assertEquals($function['headers']['status-code'], 201); $this->assertNotEmpty($function['body']['$id']); - $folder = 'timeout'; - $output = ''; - $code = realpath(__DIR__ . '/../../../resources/functions') . "/{$folder}/code.tar.gz"; - - Console::execute('cd ' . realpath(__DIR__ . "/../../../resources/functions") . "/{$folder} && tar --exclude code.tar.gz -czf code.tar.gz .", '', $output); - $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', array_merge([ 'content-type' => 'multipart/form-data', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ 'entrypoint' => 'index.php', - 'code' => new CURLFile($code, 'application/x-gzip', basename($code)), + 'code' => $this->packageFunction('timeout'), 'activate' => true ]); From f24863349e133f51712aae84ba686ae86eaf3437 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Fri, 20 Sep 2024 09:41:34 +0100 Subject: [PATCH 18/33] fixes --- .../Functions/FunctionsCustomServerTest.php | 24 ++++++++++--------- tests/e2e/Services/Realtime/RealtimeBase.php | 21 ---------------- .../Realtime/RealtimeCustomClientTest.php | 2 ++ 3 files changed, 15 insertions(+), 32 deletions(-) diff --git a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php index e9297a109f..d95d611f13 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php @@ -176,25 +176,25 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals($functions['body']['functions'][0]['name'], 'Test'); $this->assertEquals($functions['body']['functions'][1]['name'], 'Test 2'); - $functions = $this->listFunctions([ + $functions1 = $this->listFunctions([ 'queries' => [ Query::cursorAfter(new Document(['$id' => $functions['body']['functions'][0]['$id']]))->toString(), ], ]); - $this->assertEquals($functions['headers']['status-code'], 200); - $this->assertCount(1, $functions['body']['functions']); - $this->assertEquals($functions['body']['functions'][0]['name'], 'Test 2'); + $this->assertEquals($functions1['headers']['status-code'], 200); + $this->assertCount(1, $functions1['body']['functions']); + $this->assertEquals($functions1['body']['functions'][0]['name'], 'Test 2'); - $functions = $this->listFunctions([ + $functions2 = $this->listFunctions([ 'queries' => [ Query::cursorBefore(new Document(['$id' => $functions['body']['functions'][1]['$id']]))->toString(), ], ]); - $this->assertEquals($functions['headers']['status-code'], 200); - $this->assertCount(1, $functions['body']['functions']); - $this->assertEquals($functions['body']['functions'][0]['name'], 'Test'); + $this->assertEquals($functions2['headers']['status-code'], 200); + $this->assertCount(1, $functions2['body']['functions']); + $this->assertEquals($functions2['body']['functions'][0]['name'], 'Test'); /** * Test for FAILURE @@ -285,7 +285,7 @@ class FunctionsCustomServerTest extends Scope { $functionId = $this->setupFunction([ 'functionId' => ID::unique(), - 'name' => 'test-cli-deployment', + 'name' => 'Test', 'execute' => [Role::user($this->getUser()['$id'])->toString()], 'runtime' => 'php-8.0', 'entrypoint' => 'index.php', @@ -297,12 +297,12 @@ class FunctionsCustomServerTest extends Scope 'timeout' => 10, ]); - $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', array_merge([ + $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', [ 'content-type' => 'multipart/form-data', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'], 'x-sdk-language' => 'cli', - ], $this->getHeaders()), [ + ], [ 'entrypoint' => 'index.php', 'code' => $this->packageFunction('php'), 'activate' => true @@ -314,6 +314,7 @@ class FunctionsCustomServerTest extends Scope $this->assertEventually(function () use ($functionId, $deploymentId) { $deployment = $this->getDeployment($functionId, $deploymentId); + $this->assertEquals(200, $deployment['headers']['status-code']); $this->assertEquals('ready', $deployment['body']['status']); $this->assertEquals('cli', $deployment['body']['type']); @@ -1212,6 +1213,7 @@ class FunctionsCustomServerTest extends Scope public function testExecutionTimeout() { $functionId = $this->setupFunction([ + 'functionId' => ID::unique(), 'name' => 'timeout-php-8.0', 'runtime' => 'php-8.0', 'entrypoint' => 'index.php', diff --git a/tests/e2e/Services/Realtime/RealtimeBase.php b/tests/e2e/Services/Realtime/RealtimeBase.php index f0cd0e7c3b..30c411ba93 100644 --- a/tests/e2e/Services/Realtime/RealtimeBase.php +++ b/tests/e2e/Services/Realtime/RealtimeBase.php @@ -2,8 +2,6 @@ namespace Tests\E2E\Services\Realtime; -use CURLFile; -use Utopia\CLI\Console; use WebSocket\Client as WebSocketClient; use WebSocket\ConnectionException; @@ -73,23 +71,4 @@ trait RealtimeBase $this->expectException(ConnectionException::class); // Check if server disconnnected client $client->close(); } - - // Function-related methods - protected string $output = ''; - - protected function packageFunction(string $function = 'php'): CURLFile - { - $folderPath = realpath(__DIR__ . '/../../../resources/functions') . "/$function"; - $tarPath = "$folderPath/code.tar.gz"; - - if (!file_exists($tarPath)) { - Console::execute("cd $folderPath && tar --exclude code.tar.gz -czf code.tar.gz .", '', $this->output); - } - - if (filesize($tarPath) > 1024 * 1024 * 5) { - throw new \Exception('Code package is too large. Use the chunked upload method instead.'); - } - - return new CURLFile($tarPath, 'application/x-gzip', \basename($tarPath)); - } } diff --git a/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php b/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php index 4c23bb2eb9..260d5da3bf 100644 --- a/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php +++ b/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php @@ -7,6 +7,7 @@ use Tests\E2E\Client; use Tests\E2E\Scopes\ProjectCustom; use Tests\E2E\Scopes\Scope; use Tests\E2E\Scopes\SideClient; +use Tests\E2E\Services\Functions\FunctionsBase; use Utopia\Database\Helpers\ID; use Utopia\Database\Helpers\Permission; use Utopia\Database\Helpers\Role; @@ -14,6 +15,7 @@ use WebSocket\ConnectionException; class RealtimeCustomClientTest extends Scope { + use FunctionsBase; use RealtimeBase; use ProjectCustom; use SideClient; From 1509aa544de051c61856fe4cc42d64fcc3ab2d3c Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Fri, 20 Sep 2024 09:43:41 +0100 Subject: [PATCH 19/33] debug: output fail deployment --- tests/e2e/Services/Functions/FunctionsBase.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/Services/Functions/FunctionsBase.php b/tests/e2e/Services/Functions/FunctionsBase.php index 8b19c65705..529846c623 100644 --- a/tests/e2e/Services/Functions/FunctionsBase.php +++ b/tests/e2e/Services/Functions/FunctionsBase.php @@ -44,7 +44,7 @@ trait FunctionsBase 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'], ])); - $this->assertEquals('ready', $deployment['body']['status']); + $this->assertEquals('ready', $deployment['body']['status'], 'Deployment status is not ready, deployment: ' . json_encode($deployment['body'], JSON_PRETTY_PRINT)); }, 50000, 500); return $deploymentId; From 39490e981988ce24ead726a47e571ad9a6053c7f Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Fri, 20 Sep 2024 10:06:58 +0100 Subject: [PATCH 20/33] chore: bump tests --- .github/workflows/tests.yml | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 5b82cba594..4bc7b18d23 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -16,22 +16,22 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: recursive - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3 - name: Build Appwrite - uses: docker/build-push-action@v3 + uses: docker/build-push-action@v6 with: context: . push: false tags: ${{ env.IMAGE }} load: true - cache-from: type=gha - cache-to: type=gha,mode=max + cache-from: type=gha,scope=appwrite + cache-to: type=gha,mode=max,scope=appwrite outputs: type=docker,dest=/tmp/${{ env.IMAGE }}.tar build-args: | DEBUG=false @@ -39,9 +39,11 @@ jobs: VERSION=dev - name: Cache Docker Image - uses: actions/cache@v3 + uses: actions/cache@v4 with: key: ${{ env.CACHE_KEY }} + restore-keys: | + appwrite-dev- path: /tmp/${{ env.IMAGE }}.tar unit_test: @@ -51,10 +53,10 @@ jobs: steps: - name: checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Load Cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: key: ${{ env.CACHE_KEY }} path: /tmp/${{ env.IMAGE }}.tar @@ -81,10 +83,10 @@ jobs: needs: setup steps: - name: checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Load Cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: key: ${{ env.CACHE_KEY }} path: /tmp/${{ env.IMAGE }}.tar @@ -129,10 +131,10 @@ jobs: steps: - name: checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Load Cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: key: ${{ env.CACHE_KEY }} path: /tmp/${{ env.IMAGE }}.tar @@ -142,7 +144,7 @@ jobs: run: | docker load --input /tmp/${{ env.IMAGE }}.tar docker compose up -d - sleep 10 + sleep 25 - name: Run ${{matrix.service}} Tests run: docker compose exec -T appwrite test /usr/src/code/tests/e2e/Services/${{matrix.service}} --debug @@ -150,15 +152,15 @@ jobs: - name: Run ${{matrix.service}} Shared Tables Tests run: _APP_DATABASE_SHARED_TABLES=database_db_main docker compose exec -T appwrite test /usr/src/code/tests/e2e/Services/${{matrix.service}} --debug - benchamrking: + benchmarking: name: Benchmark runs-on: ubuntu-latest needs: setup steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Load Cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: key: ${{ env.CACHE_KEY }} path: /tmp/${{ env.IMAGE }}.tar From 8e13aafbc854ff8cba1b1b62c2683af97ca42bc6 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Fri, 20 Sep 2024 10:29:49 +0100 Subject: [PATCH 21/33] fixes --- .../Functions/FunctionsCustomServerTest.php | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php index d95d611f13..66f77a1df8 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php @@ -454,30 +454,34 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals(true, (new DatetimeValidator())->isValid($deployment['body']['$createdAt'])); $this->assertEquals('index.php', $deployment['body']['entrypoint']); - $deploymentId = $deployment['body']['$id'] ?? ''; + $deploymentIdActive = $deployment['body']['$id'] ?? ''; + + $this->assertEventually(function () use ($functionId, $deploymentIdActive) { + $deployment = $this->getDeployment($functionId, $deploymentIdActive); + + $this->assertEquals('ready', $deployment['body']['status']); + }, 50000, 500); $deployment = $this->createDeployment($functionId, [ 'code' => $this->packageFunction('php'), 'activate' => false ]); - $deploymentIdInactive = $deployment['body']['$id']; - $this->assertEquals(202, $deployment['headers']['status-code']); $this->assertNotEmpty($deployment['body']['$id']); - $this->assertEventually(function () use ($functionId, $deploymentId, $deploymentIdInactive) { - $deployment = $this->getDeployment($functionId, $deploymentId); - $this->assertEquals('ready', $deployment['body']['status']); + $deploymentIdInactive = $deployment['body']['$id'] ?? ''; + $this->assertEventually(function () use ($functionId, $deploymentIdInactive) { $deployment = $this->getDeployment($functionId, $deploymentIdInactive); + $this->assertEquals('ready', $deployment['body']['status']); - }, 500000, 1000); + }, 50000, 500); $function = $this->getFunction($functionId); $this->assertEquals(200, $function['headers']['status-code']); - $this->assertEquals($deploymentId, $function['body']['deployment']); + $this->assertEquals($deploymentIdActive, $function['body']['deployment']); $this->assertNotEquals($deploymentIdInactive, $function['body']['deployment']); $deployment = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId . '/deployments/' . $deploymentIdInactive, array_merge([ @@ -487,7 +491,7 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals(204, $deployment['headers']['status-code']); - return array_merge($data, ['deploymentId' => $deploymentId]); + return array_merge($data, ['deploymentId' => $deploymentIdActive]); } /** @@ -1222,7 +1226,7 @@ class FunctionsCustomServerTest extends Scope 'timeout' => 5, // Should timeout after 5 seconds ]); $this->setupDeployment($functionId, [ - 'code' => $this->packageFunction('php'), + 'code' => $this->packageFunction('timeout'), 'activate' => true, ]); @@ -1562,7 +1566,7 @@ class FunctionsCustomServerTest extends Scope $functionId = $this->setupFunction([ 'functionId' => ID::unique(), 'name' => 'Test PHP Scopes executions', - 'commands' => 'composer update --no-interaction --ignore-platform-reqs --optimize-autoloader --prefer-dist --no-dev', + 'commands' => 'sh setup.sh && composer install', 'runtime' => 'php-8.0', 'entrypoint' => 'index.php', 'scopes' => ['users.read'], @@ -1571,7 +1575,6 @@ class FunctionsCustomServerTest extends Scope $deploymentId = $this->setupDeployment($functionId, [ 'entrypoint' => 'index.php', - 'commands' => 'sh setup.sh && composer install', 'code' => $this->packageFunction('php-scopes'), 'activate' => true, ]); From 41490602f77b59677d64c9e6b67af1ee1b52d774 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Fri, 20 Sep 2024 10:38:43 +0100 Subject: [PATCH 22/33] fix: template --- tests/e2e/Services/Functions/FunctionsCustomServerTest.php | 1 - tests/e2e/Services/FunctionsSchedule/FunctionsScheduleTest.php | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php index 66f77a1df8..7021030331 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php @@ -375,7 +375,6 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals(200, $deployment['headers']['status-code']); $this->assertEquals('ready', $deployment['body']['status']); - $this->assertEquals('cli', $deployment['body']['type']); }, 500000, 1000); $function = $this->getFunction($functionId); diff --git a/tests/e2e/Services/FunctionsSchedule/FunctionsScheduleTest.php b/tests/e2e/Services/FunctionsSchedule/FunctionsScheduleTest.php index 7e36662e9e..bee15693f1 100644 --- a/tests/e2e/Services/FunctionsSchedule/FunctionsScheduleTest.php +++ b/tests/e2e/Services/FunctionsSchedule/FunctionsScheduleTest.php @@ -62,7 +62,7 @@ class FunctionsScheduleTest extends Scope $this->assertNotEmpty($asyncExecution['logs']); $this->assertNotEmpty($asyncExecution['errors']); $this->assertGreaterThan(0, $asyncExecution['duration']); - }, 10000, 500); + }, 15000, 500); $this->cleanupFunction($functionId); } From 2d102a122c4c1f8c60110770a6285bbe445e33ab Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Fri, 20 Sep 2024 13:42:08 +0100 Subject: [PATCH 23/33] fix: tests --- .../Functions/FunctionsCustomServerTest.php | 47 +++++-------------- .../FunctionsScheduleTest.php | 2 +- 2 files changed, 13 insertions(+), 36 deletions(-) diff --git a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php index 7021030331..fec815c2ef 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php @@ -463,7 +463,7 @@ class FunctionsCustomServerTest extends Scope $deployment = $this->createDeployment($functionId, [ 'code' => $this->packageFunction('php'), - 'activate' => false + 'activate' => 'false' ]); $this->assertEquals(202, $deployment['headers']['status-code']); @@ -502,7 +502,7 @@ class FunctionsCustomServerTest extends Scope $deployment = $this->createDeployment($functionId, [ 'code' => $this->packageFunction('php'), - 'activate' => false + 'activate' => 'false' ]); $deploymentId = $deployment['body']['$id'] ?? ''; @@ -646,18 +646,6 @@ class FunctionsCustomServerTest extends Scope $this->assertArrayHasKey('size', $deployments['body']['deployments'][0]); $this->assertArrayHasKey('buildSize', $deployments['body']['deployments'][0]); - // Test search id - $deployments = $this->listDeployments($functionId, [ - 'search' => $data['functionId'] - ]); - - $this->assertEquals($deployments['headers']['status-code'], 200); - $this->assertEquals(3, $deployments['body']['total']); - $this->assertIsArray($deployments['body']['deployments']); - $this->assertCount(3, $deployments['body']['deployments']); - $this->assertEquals($deployments['body']['deployments'][0]['$id'], $data['deploymentId']); - - // Test pagination limit $deployments = $this->listDeployments($functionId, [ 'queries' => [ Query::limit(1)->toString(), @@ -694,16 +682,6 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals($deployments['headers']['status-code'], 200); $this->assertCount(0, $deployments['body']['deployments']); - $deployments = $this->listDeployments($functionId, [ - 'search' => 'Test' - ]); - - $this->assertEquals($deployments['headers']['status-code'], 200); - $this->assertEquals(3, $deployments['body']['total']); - $this->assertIsArray($deployments['body']['deployments']); - $this->assertCount(3, $deployments['body']['deployments']); - $this->assertEquals($deployments['body']['deployments'][0]['$id'], $data['deploymentId']); - $deployments = $this->listDeployments($functionId, [ 'search' => 'php-8.0' ]); @@ -818,7 +796,7 @@ class FunctionsCustomServerTest extends Scope $matchingDeployment = array_filter( $deployments['body']['deployments'], - fn ($deployment) => $deployment['$id'] === $deploymentId + fn($deployment) => $deployment['$id'] === $deploymentId ); $this->assertNotEmpty($matchingDeployment, "Deployment with ID {$deploymentId} not found"); @@ -905,8 +883,6 @@ class FunctionsCustomServerTest extends Scope 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), []); - $execution = $this->createExecution($data['functionId']); - $this->assertEquals(204, $execution['headers']['status-code']); return array_merge($data, ['executionId' => $executionId]); @@ -922,11 +898,12 @@ class FunctionsCustomServerTest extends Scope */ $executions = $this->listExecutions($data['functionId']); - $this->assertEquals($executions['headers']['status-code'], 200); - $this->assertEquals($executions['body']['total'], 1); + fwrite(STDERR, print_r(json_encode($executions, JSON_PRETTY_PRINT), true)); + + $this->assertEquals(200, $executions['headers']['status-code']); + $this->assertEquals(1, $executions['body']['total']); $this->assertIsArray($executions['body']['executions']); $this->assertCount(1, $executions['body']['executions']); - $this->assertEquals($executions['body']['executions'][0]['$id'], $data['executionId']); $executions = $this->listExecutions($data['functionId'], [ 'queries' => [ @@ -939,12 +916,12 @@ class FunctionsCustomServerTest extends Scope $executions = $this->listExecutions($data['functionId'], [ 'queries' => [ - Query::offset(1)->toString(), + Query::offset(0)->toString(), ], ]); $this->assertEquals(200, $executions['headers']['status-code']); - $this->assertCount(0, $executions['body']['executions']); + $this->assertCount(1, $executions['body']['executions']); $executions = $this->listExecutions($data['functionId'], [ 'queries' => [ @@ -1217,7 +1194,7 @@ class FunctionsCustomServerTest extends Scope { $functionId = $this->setupFunction([ 'functionId' => ID::unique(), - 'name' => 'timeout-php-8.0', + 'name' => 'Test php-8.0', 'runtime' => 'php-8.0', 'entrypoint' => 'index.php', 'events' => [], @@ -1612,8 +1589,8 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals('completed', $execution['body']['status']); $this->assertEquals(200, $execution['body']['responseStatusCode']); $this->assertGreaterThan(0, $execution['body']['duration']); - $this->assertNotEmpty($execution['body']['responseBody']); - $this->assertStringContainsString("total", $execution['body']['responseBody']); + $this->assertNotEmpty($execution['body']['logs']); + $this->assertStringContainsString("total", $execution['body']['logs']); }, 10000, 500); $this->cleanupFunction($functionId); diff --git a/tests/e2e/Services/FunctionsSchedule/FunctionsScheduleTest.php b/tests/e2e/Services/FunctionsSchedule/FunctionsScheduleTest.php index bee15693f1..cbe54b6445 100644 --- a/tests/e2e/Services/FunctionsSchedule/FunctionsScheduleTest.php +++ b/tests/e2e/Services/FunctionsSchedule/FunctionsScheduleTest.php @@ -62,7 +62,7 @@ class FunctionsScheduleTest extends Scope $this->assertNotEmpty($asyncExecution['logs']); $this->assertNotEmpty($asyncExecution['errors']); $this->assertGreaterThan(0, $asyncExecution['duration']); - }, 15000, 500); + }, 60000, 500); $this->cleanupFunction($functionId); } From cefc7a1d4baf4ac670125d8ff491617092b6687d Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Fri, 20 Sep 2024 13:44:34 +0100 Subject: [PATCH 24/33] chore: fmt --- tests/e2e/Services/Functions/FunctionsCustomServerTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php index fec815c2ef..050c0681c5 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php @@ -796,7 +796,7 @@ class FunctionsCustomServerTest extends Scope $matchingDeployment = array_filter( $deployments['body']['deployments'], - fn($deployment) => $deployment['$id'] === $deploymentId + fn ($deployment) => $deployment['$id'] === $deploymentId ); $this->assertNotEmpty($matchingDeployment, "Deployment with ID {$deploymentId} not found"); From 42ae429153156a6b20d7fd92cfbef699e02631ef Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Fri, 20 Sep 2024 13:49:29 +0100 Subject: [PATCH 25/33] chore: remove test --- tests/e2e/Services/Functions/FunctionsCustomServerTest.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php index 050c0681c5..a0395a08b0 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php @@ -898,8 +898,6 @@ class FunctionsCustomServerTest extends Scope */ $executions = $this->listExecutions($data['functionId']); - fwrite(STDERR, print_r(json_encode($executions, JSON_PRETTY_PRINT), true)); - $this->assertEquals(200, $executions['headers']['status-code']); $this->assertEquals(1, $executions['body']['total']); $this->assertIsArray($executions['body']['executions']); From df9e80a9629c638a276f96717096e406b71f98c2 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Fri, 20 Sep 2024 17:02:23 +0100 Subject: [PATCH 26/33] feat: handle bools --- tests/e2e/Client.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/e2e/Client.php b/tests/e2e/Client.php index 0774f1c6fd..0595611570 100644 --- a/tests/e2e/Client.php +++ b/tests/e2e/Client.php @@ -303,6 +303,8 @@ class Client if (is_array($value)) { $output += $this->flatten($value, $finalKey); // @todo: handle name collision here if needed + } elseif (is_bool($value)) { + $output[$finalKey] = $value ? 'true' : 'false'; } else { $output[$finalKey] = $value; } From f0485ca87010df935785a3c4a8c29c47ca3d034e Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Fri, 20 Sep 2024 18:30:26 +0100 Subject: [PATCH 27/33] fix: use boolean false --- tests/e2e/Services/Functions/FunctionsCustomServerTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php index a0395a08b0..9166a7c4e8 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php @@ -463,7 +463,7 @@ class FunctionsCustomServerTest extends Scope $deployment = $this->createDeployment($functionId, [ 'code' => $this->packageFunction('php'), - 'activate' => 'false' + 'activate' => false ]); $this->assertEquals(202, $deployment['headers']['status-code']); @@ -502,7 +502,7 @@ class FunctionsCustomServerTest extends Scope $deployment = $this->createDeployment($functionId, [ 'code' => $this->packageFunction('php'), - 'activate' => 'false' + 'activate' => false ]); $deploymentId = $deployment['body']['$id'] ?? ''; From f6e3759f6b05cb87a446c982e94ce91518ed663a Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Sun, 22 Sep 2024 20:14:58 +0100 Subject: [PATCH 28/33] chore: add async --- tests/e2e/Services/Functions/FunctionsBase.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/e2e/Services/Functions/FunctionsBase.php b/tests/e2e/Services/Functions/FunctionsBase.php index badab22147..15acce8ba2 100644 --- a/tests/e2e/Services/Functions/FunctionsBase.php +++ b/tests/e2e/Services/Functions/FunctionsBase.php @@ -2,12 +2,15 @@ namespace Tests\E2E\Services\Functions; +use Appwrite\Tests\Async; use CURLFile; use Tests\E2E\Client; use Utopia\CLI\Console; trait FunctionsBase { + use Async; + protected string $stdout = ''; protected string $stderr = ''; From 0753163066d24591688bee6f095acf12703c4d74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Wed, 25 Sep 2024 13:32:09 +0200 Subject: [PATCH 29/33] PR review changes --- tests/e2e/Client.php | 2 -- tests/e2e/Services/Functions/FunctionsBase.php | 11 ++++------- tests/e2e/Services/GraphQL/Base.php | 6 ++---- tests/extensions/Async/Eventually.php | 7 +------ 4 files changed, 7 insertions(+), 19 deletions(-) diff --git a/tests/e2e/Client.php b/tests/e2e/Client.php index 0595611570..0774f1c6fd 100644 --- a/tests/e2e/Client.php +++ b/tests/e2e/Client.php @@ -303,8 +303,6 @@ class Client if (is_array($value)) { $output += $this->flatten($value, $finalKey); // @todo: handle name collision here if needed - } elseif (is_bool($value)) { - $output[$finalKey] = $value ? 'true' : 'false'; } else { $output[$finalKey] = $value; } diff --git a/tests/e2e/Services/Functions/FunctionsBase.php b/tests/e2e/Services/Functions/FunctionsBase.php index 15acce8ba2..a1bb8f2b21 100644 --- a/tests/e2e/Services/Functions/FunctionsBase.php +++ b/tests/e2e/Services/Functions/FunctionsBase.php @@ -142,14 +142,12 @@ trait FunctionsBase return $executions; } - protected function packageFunction(string $function = 'php'): CURLFile + protected function packageFunction(string $function): CURLFile { $folderPath = realpath(__DIR__ . '/../../../resources/functions') . "/$function"; $tarPath = "$folderPath/code.tar.gz"; - if (!file_exists($tarPath)) { - Console::execute("cd $folderPath && tar --exclude code.tar.gz -czf code.tar.gz .", '', $this->stdout, $this->stderr); - } + Console::execute("cd $folderPath && tar --exclude code.tar.gz -czf code.tar.gz .", '', $this->stdout, $this->stderr); if (filesize($tarPath) > 1024 * 1024 * 5) { throw new \Exception('Code package is too large. Use the chunked upload method instead.'); @@ -200,11 +198,10 @@ trait FunctionsBase protected function deleteFunction(string $functionId): mixed { - $function = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, [ + $function = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ]); + ], $this->getHeaders())); return $function; } diff --git a/tests/e2e/Services/GraphQL/Base.php b/tests/e2e/Services/GraphQL/Base.php index 01f9508a81..0b3250cecf 100644 --- a/tests/e2e/Services/GraphQL/Base.php +++ b/tests/e2e/Services/GraphQL/Base.php @@ -2497,14 +2497,12 @@ trait Base protected string $stdout = ''; protected string $stderr = ''; - protected function packageFunction(string $function = 'php'): CURLFile + protected function packageFunction(string $function): CURLFile { $folderPath = realpath(__DIR__ . '/../../../resources/functions') . "/$function"; $tarPath = "$folderPath/code.tar.gz"; - if (!file_exists($tarPath)) { - Console::execute("cd $folderPath && tar --exclude code.tar.gz -czf code.tar.gz .", '', $this->stdout, $this->stderr); - } + Console::execute("cd $folderPath && tar --exclude code.tar.gz -czf code.tar.gz .", '', $this->stdout, $this->stderr); if (filesize($tarPath) > 1024 * 1024 * 5) { throw new \Exception('Code package is too large. Use the chunked upload method instead.'); diff --git a/tests/extensions/Async/Eventually.php b/tests/extensions/Async/Eventually.php index e4987de410..9840b1a114 100644 --- a/tests/extensions/Async/Eventually.php +++ b/tests/extensions/Async/Eventually.php @@ -6,13 +6,8 @@ use PHPUnit\Framework\Constraint\Constraint; final class Eventually extends Constraint { - private int $timeoutMs; - private int $waitMs; - - public function __construct(int $timeoutMs, int $waitMs) + public function __construct(private int $timeoutMs, private int $waitMs) { - $this->timeoutMs = $timeoutMs; - $this->waitMs = $waitMs; } public function evaluate(mixed $probe, string $description = '', bool $returnResult = false): ?bool From 05bcb92f73a34d2c175aba50151ec52502ee37c1 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Mon, 30 Sep 2024 14:45:06 +0100 Subject: [PATCH 30/33] test: extra sleep --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 4bc7b18d23..6e7012527d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -144,7 +144,7 @@ jobs: run: | docker load --input /tmp/${{ env.IMAGE }}.tar docker compose up -d - sleep 25 + sleep 30 - name: Run ${{matrix.service}} Tests run: docker compose exec -T appwrite test /usr/src/code/tests/e2e/Services/${{matrix.service}} --debug From a45db60313b52115eadbbfb0ddc3b4d473c71942 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Wed, 2 Oct 2024 14:16:25 +0100 Subject: [PATCH 31/33] fix: falsy values --- tests/e2e/General/UsageTest.php | 4 ++-- .../Functions/FunctionsCustomClientTest.php | 4 ++-- .../Functions/FunctionsCustomServerTest.php | 18 +++++++++--------- .../FunctionsScheduleTest.php | 2 +- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/tests/e2e/General/UsageTest.php b/tests/e2e/General/UsageTest.php index db75a5db45..c50a15fd3f 100644 --- a/tests/e2e/General/UsageTest.php +++ b/tests/e2e/General/UsageTest.php @@ -931,7 +931,7 @@ class UsageTest extends Scope 'x-appwrite-project' => $this->getProject()['$id'] ], $this->getHeaders()), [ - 'async' => false, + 'async' => 'false', ] ); @@ -955,7 +955,7 @@ class UsageTest extends Scope 'x-appwrite-project' => $this->getProject()['$id'] ], $this->getHeaders()), [ - 'async' => false, + 'async' => 'false', ] ); diff --git a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php index 3447ee0547..31cc05f423 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php @@ -97,7 +97,7 @@ class FunctionsCustomClientTest extends Scope $execution = $this->createExecution($functionId, [ 'body' => 'foobar', - 'async' => false + 'async' => 'false' ]); $output = json_decode($execution['body']['responseBody'], true); $this->assertEquals(201, $execution['headers']['status-code']); @@ -208,7 +208,7 @@ class FunctionsCustomClientTest extends Scope $execution = $this->createExecution($functionId, [ 'body' => 'foobar', - // Testing default value, should be 'async' => false + // Testing default value, should be 'async' => 'false' ]); $output = json_decode($execution['body']['responseBody'], true); $this->assertEquals(201, $execution['headers']['status-code']); diff --git a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php index 14ec660c4b..2c3ddbcf58 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php @@ -463,7 +463,7 @@ class FunctionsCustomServerTest extends Scope $deployment = $this->createDeployment($functionId, [ 'code' => $this->packageFunction('php'), - 'activate' => false + 'activate' => 'false' ]); $this->assertEquals(202, $deployment['headers']['status-code']); @@ -502,7 +502,7 @@ class FunctionsCustomServerTest extends Scope $deployment = $this->createDeployment($functionId, [ 'code' => $this->packageFunction('php'), - 'activate' => false + 'activate' => 'false' ]); $deploymentId = $deployment['body']['$id'] ?? ''; @@ -845,7 +845,7 @@ class FunctionsCustomServerTest extends Scope * Test for SUCCESS */ $execution = $this->createExecution($data['functionId'], [ - 'async' => false, + 'async' => 'false', ]); $this->assertEquals(201, $execution['headers']['status-code']); @@ -870,7 +870,7 @@ class FunctionsCustomServerTest extends Scope $executionId = $execution['body']['$id'] ?? ''; $execution = $this->createExecution($data['functionId'], [ - 'async' => false, + 'async' => 'false', 'path' => '/?code=400' ]); @@ -965,7 +965,7 @@ class FunctionsCustomServerTest extends Scope * Test for SUCCESS */ $execution = $this->createExecution($data['functionId'], [ - // Testing default value, should be 'async' => false + // Testing default value, should be 'async' => 'false' ]); $this->assertEquals(201, $execution['headers']['status-code']); @@ -1281,7 +1281,7 @@ class FunctionsCustomServerTest extends Scope $execution = $this->createExecution($functionId, [ 'body' => 'foobar', - 'async' => false + 'async' => 'false' ]); $output = json_decode($execution['body']['responseBody'], true); @@ -1447,7 +1447,7 @@ class FunctionsCustomServerTest extends Scope $execution = $this->createExecution($functionId, [ 'body' => 'foobar', - 'async' => false + 'async' => 'false' ]); $output = json_decode($execution['body']['responseBody'], true); @@ -1561,7 +1561,7 @@ class FunctionsCustomServerTest extends Scope $this->assertStringContainsStringIgnoringCase('"users":', $deployment['body']['buildLogs']); $execution = $this->createExecution($functionId, [ - 'async' => false, + 'async' => 'false', ]); $this->assertEquals(201, $execution['headers']['status-code']); @@ -1611,7 +1611,7 @@ class FunctionsCustomServerTest extends Scope $cookie = 'cookieName=cookieValue; cookie2=value2; cookie3=value=3; cookie4=val:ue4; cookie5=value5'; $execution = $this->createExecution($functionId, [ - 'async' => false, + 'async' => 'false', 'headers' => [ 'cookie' => $cookie ] diff --git a/tests/e2e/Services/FunctionsSchedule/FunctionsScheduleTest.php b/tests/e2e/Services/FunctionsSchedule/FunctionsScheduleTest.php index cbe54b6445..b1315103b1 100644 --- a/tests/e2e/Services/FunctionsSchedule/FunctionsScheduleTest.php +++ b/tests/e2e/Services/FunctionsSchedule/FunctionsScheduleTest.php @@ -143,7 +143,7 @@ class FunctionsScheduleTest extends Scope /* Test for FAILURE */ // Schedule synchronous execution $execution = $this->createExecution($functionId, [ - 'async' => false, + 'async' => 'false', 'scheduledAt' => $futureTime->format(\DateTime::ATOM), ]); $this->assertEquals(400, $execution['headers']['status-code']); From 886687ed34fc275b29bfa6c542419555c85d8320 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Wed, 2 Oct 2024 15:19:15 +0100 Subject: [PATCH 32/33] fix: reorder assertions --- tests/e2e/Services/Functions/FunctionsCustomServerTest.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php index 2c3ddbcf58..3af050438f 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php @@ -1450,10 +1450,11 @@ class FunctionsCustomServerTest extends Scope 'async' => 'false' ]); - $output = json_decode($execution['body']['responseBody'], true); $this->assertEquals(201, $execution['headers']['status-code']); $this->assertEquals('completed', $execution['body']['status']); $this->assertEquals(200, $execution['body']['responseStatusCode']); + + $output = json_decode($execution['body']['responseBody'], true); $this->assertEquals(true, $output['v2Woks']); $this->cleanupFunction($functionId); From 671f801cd571ac8826475a63021c8a72109c4598 Mon Sep 17 00:00:00 2001 From: Binyamin Yawitz <316103+byawitz@users.noreply.github.com> Date: Wed, 2 Oct 2024 13:26:45 -0400 Subject: [PATCH 33/33] fix: missing protocol --- app/config/specs/open-api3-1.6.x-client.json | 45 +++---- app/config/specs/open-api3-1.6.x-console.json | 116 +++++++++--------- app/config/specs/open-api3-1.6.x-server.json | 111 ++++++++--------- app/config/specs/open-api3-latest-client.json | 45 +++---- .../specs/open-api3-latest-console.json | 116 +++++++++--------- app/config/specs/open-api3-latest-server.json | 111 ++++++++--------- app/config/specs/swagger2-1.6.x-client.json | 45 +++---- app/config/specs/swagger2-1.6.x-console.json | 116 +++++++++--------- app/config/specs/swagger2-1.6.x-server.json | 111 ++++++++--------- app/config/specs/swagger2-latest-client.json | 45 +++---- app/config/specs/swagger2-latest-console.json | 116 +++++++++--------- app/config/specs/swagger2-latest-server.json | 111 ++++++++--------- app/controllers/api/projects.php | 2 +- 13 files changed, 553 insertions(+), 537 deletions(-) diff --git a/app/config/specs/open-api3-1.6.x-client.json b/app/config/specs/open-api3-1.6.x-client.json index f1b247a70f..021ae27c45 100644 --- a/app/config/specs/open-api3-1.6.x-client.json +++ b/app/config/specs/open-api3-1.6.x-client.json @@ -166,7 +166,7 @@ "tags": [ "account" ], - "description": "Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. 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.\n", + "description": "Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. For security measures, user password is required to complete this request.\r\nThis endpoint can also be used to convert an anonymous account to a normal one, by passing an email address and a new password.\r\n", "responses": { "200": { "description": "User", @@ -1570,7 +1570,7 @@ "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](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createRecovery) 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.", + "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](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createRecovery) endpoint.\r\n\r\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", @@ -1802,7 +1802,7 @@ "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.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", + "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.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", "responses": { "201": { "description": "Session", @@ -1954,7 +1954,7 @@ "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.\n\nIf there is already an active session, the new session will be attached to the logged-in account. If there are no active sessions, the server will attempt to look for a user with the same email address as the email received from the OAuth2 provider and attach the new session to the existing user. If no matching user is found - the server will create a new user.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\n", + "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.\r\n\r\nIf there is already an active session, the new session will be attached to the logged-in account. If there are no active sessions, the server will attempt to look for a user with the same email address as the email received from the OAuth2 provider and attach the new session to the existing user. If no matching user is found - the server will create a new user.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\r\n", "responses": { "301": { "description": "File" @@ -2703,7 +2703,7 @@ "tags": [ "account" ], - "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's email is valid for 15 minutes.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", + "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's email is valid for 15 minutes.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", "responses": { "201": { "description": "Token", @@ -2784,7 +2784,7 @@ "tags": [ "account" ], - "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. If you are on a mobile device you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\n", + "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. If you are on a mobile device you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\r\n", "responses": { "201": { "description": "Token", @@ -2873,7 +2873,7 @@ "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. \n\nIf authentication succeeds, `userId` and `secret` of a token will be appended to the success URL as query parameters. These can be used to create a new session using the [Create session](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", + "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. \r\n\r\nIf authentication succeeds, `userId` and `secret` of a token will be appended to the success URL as query parameters. These can be used to create a new session using the [Create session](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", "responses": { "301": { "description": "File" @@ -3009,7 +3009,7 @@ "tags": [ "account" ], - "description": "Sends the user an SMS with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's phone is valid for 15 minutes.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", + "description": "Sends the user an SMS with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's phone is valid for 15 minutes.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", "responses": { "201": { "description": "Token", @@ -3088,7 +3088,7 @@ "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](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). 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", + "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](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\r\n\r\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.\r\n", "responses": { "201": { "description": "Token", @@ -3368,7 +3368,7 @@ "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 [GET \/account\/sessions](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#getSessions) endpoint. Use width, height and quality arguments to change the output settings.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.", + "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 [GET \/account\/sessions](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#getSessions) endpoint. Use width, height and quality arguments to change the output settings.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.", "responses": { "200": { "description": "Image" @@ -3496,7 +3496,7 @@ "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.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n", + "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.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n", "responses": { "200": { "description": "Image" @@ -3628,7 +3628,7 @@ "tags": [ "avatars" ], - "description": "Use this endpoint to fetch the favorite icon (AKA favicon) of any remote website URL.\n\nThis endpoint does not follow HTTP redirects.", + "description": "Use this endpoint to fetch the favorite icon (AKA favicon) of any remote website URL.\r\n\r\nThis endpoint does not follow HTTP redirects.", "responses": { "200": { "description": "Image" @@ -3688,7 +3688,7 @@ "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. Country codes follow the [ISO 3166-1](https:\/\/en.wikipedia.org\/wiki\/ISO_3166-1) standard.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n", + "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. Country codes follow the [ISO 3166-1](https:\/\/en.wikipedia.org\/wiki\/ISO_3166-1) standard.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n", "responses": { "200": { "description": "Image" @@ -4178,7 +4178,7 @@ "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.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 400x400px.\n\nThis endpoint does not follow HTTP redirects.", + "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.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 400x400px.\r\n\r\nThis endpoint does not follow HTTP redirects.", "responses": { "200": { "description": "Image" @@ -4262,7 +4262,7 @@ "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.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n", + "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.\r\n\r\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.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n", "responses": { "200": { "description": "Image" @@ -4356,7 +4356,7 @@ "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.\n", + "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.\r\n", "responses": { "200": { "description": "Image" @@ -5319,7 +5319,7 @@ "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))", + "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.\r\n\r\n([IP Geolocation by DB-IP](https:\/\/db-ip.com))", "responses": { "200": { "description": "Locale", @@ -6001,7 +6001,7 @@ "tags": [ "storage" ], - "description": "Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/storage#storageCreateBucket) API or directly from your Appwrite console.\n\nLarger files should be uploaded using multiple requests with the [content-range](https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Content-Range) header to send a partial request with a maximum supported chunk of `5MB`. The `content-range` header values should always be in bytes.\n\nWhen the first request is sent, the server will return the **File** object, and the subsequent part request must include the file's **id** in `x-appwrite-id` header to allow the server to know that the partial upload is for the existing file and not for a new one.\n\nIf you're creating a new file using one of the Appwrite SDKs, all the chunking logic will be managed by the SDK internally.\n", + "description": "Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/storage#storageCreateBucket) API or directly from your Appwrite console.\r\n\r\nLarger files should be uploaded using multiple requests with the [content-range](https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Content-Range) header to send a partial request with a maximum supported chunk of `5MB`. The `content-range` header values should always be in bytes.\r\n\r\nWhen the first request is sent, the server will return the **File** object, and the subsequent part request must include the file's **id** in `x-appwrite-id` header to allow the server to know that the partial upload is for the existing file and not for a new one.\r\n\r\nIf you're creating a new file using one of the Appwrite SDKs, all the chunking logic will be managed by the SDK internally.\r\n", "responses": { "201": { "description": "File", @@ -6610,7 +6610,8 @@ "jpeg", "gif", "png", - "webp" + "webp", + "avif" ], "x-enum-name": "ImageFormat", "x-enum-keys": [], @@ -7156,7 +7157,7 @@ "tags": [ "teams" ], - "description": "Invite a new member to join your team. Provide an ID for existing users, or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team.\n\nYou only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters.\n\nUse the `url` parameter to redirect the user from the invitation email to your app. After the user is redirected, use the [Update Team Membership Status](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team. \n\nPlease note that to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console.\n", + "description": "Invite a new member to join your team. Provide an ID for existing users, or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team.\r\n\r\nYou only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters.\r\n\r\nUse the `url` parameter to redirect the user from the invitation email to your app. After the user is redirected, use the [Update Team Membership Status](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team. \r\n\r\nPlease note that to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console.\r\n", "responses": { "201": { "description": "Membership", @@ -7343,7 +7344,7 @@ "tags": [ "teams" ], - "description": "Modify the roles of a team member. Only team members with the owner role have access to this endpoint. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions).\n", + "description": "Modify the roles of a team member. Only team members with the owner role have access to this endpoint. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions).\r\n", "responses": { "200": { "description": "Membership", @@ -7508,7 +7509,7 @@ "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 received by the user.\n\nIf the request is successful, a session for the user is automatically created.\n", + "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 received by the user.\r\n\r\nIf the request is successful, a session for the user is automatically created.\r\n", "responses": { "200": { "description": "Membership", diff --git a/app/config/specs/open-api3-1.6.x-console.json b/app/config/specs/open-api3-1.6.x-console.json index 1e3c1a21c4..12163dd464 100644 --- a/app/config/specs/open-api3-1.6.x-console.json +++ b/app/config/specs/open-api3-1.6.x-console.json @@ -206,7 +206,7 @@ "tags": [ "account" ], - "description": "Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. 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.\n", + "description": "Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. For security measures, user password is required to complete this request.\r\nThis endpoint can also be used to convert an anonymous account to a normal one, by passing an email address and a new password.\r\n", "responses": { "200": { "description": "User", @@ -1591,7 +1591,7 @@ "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](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createRecovery) 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.", + "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](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createRecovery) endpoint.\r\n\r\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", @@ -1820,7 +1820,7 @@ "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.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", + "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.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", "responses": { "201": { "description": "Session", @@ -1972,7 +1972,7 @@ "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.\n\nIf there is already an active session, the new session will be attached to the logged-in account. If there are no active sessions, the server will attempt to look for a user with the same email address as the email received from the OAuth2 provider and attach the new session to the existing user. If no matching user is found - the server will create a new user.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\n", + "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.\r\n\r\nIf there is already an active session, the new session will be attached to the logged-in account. If there are no active sessions, the server will attempt to look for a user with the same email address as the email received from the OAuth2 provider and attach the new session to the existing user. If no matching user is found - the server will create a new user.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\r\n", "responses": { "301": { "description": "File" @@ -2714,7 +2714,7 @@ "tags": [ "account" ], - "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's email is valid for 15 minutes.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", + "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's email is valid for 15 minutes.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", "responses": { "201": { "description": "Token", @@ -2795,7 +2795,7 @@ "tags": [ "account" ], - "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. If you are on a mobile device you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\n", + "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. If you are on a mobile device you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\r\n", "responses": { "201": { "description": "Token", @@ -2884,7 +2884,7 @@ "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. \n\nIf authentication succeeds, `userId` and `secret` of a token will be appended to the success URL as query parameters. These can be used to create a new session using the [Create session](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", + "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. \r\n\r\nIf authentication succeeds, `userId` and `secret` of a token will be appended to the success URL as query parameters. These can be used to create a new session using the [Create session](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", "responses": { "301": { "description": "File" @@ -3020,7 +3020,7 @@ "tags": [ "account" ], - "description": "Sends the user an SMS with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's phone is valid for 15 minutes.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", + "description": "Sends the user an SMS with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's phone is valid for 15 minutes.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", "responses": { "201": { "description": "Token", @@ -3099,7 +3099,7 @@ "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](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). 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", + "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](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\r\n\r\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.\r\n", "responses": { "201": { "description": "Token", @@ -3375,7 +3375,7 @@ "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 [GET \/account\/sessions](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#getSessions) endpoint. Use width, height and quality arguments to change the output settings.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.", + "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 [GET \/account\/sessions](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#getSessions) endpoint. Use width, height and quality arguments to change the output settings.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.", "responses": { "200": { "description": "Image" @@ -3503,7 +3503,7 @@ "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.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n", + "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.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n", "responses": { "200": { "description": "Image" @@ -3635,7 +3635,7 @@ "tags": [ "avatars" ], - "description": "Use this endpoint to fetch the favorite icon (AKA favicon) of any remote website URL.\n\nThis endpoint does not follow HTTP redirects.", + "description": "Use this endpoint to fetch the favorite icon (AKA favicon) of any remote website URL.\r\n\r\nThis endpoint does not follow HTTP redirects.", "responses": { "200": { "description": "Image" @@ -3695,7 +3695,7 @@ "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. Country codes follow the [ISO 3166-1](https:\/\/en.wikipedia.org\/wiki\/ISO_3166-1) standard.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n", + "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. Country codes follow the [ISO 3166-1](https:\/\/en.wikipedia.org\/wiki\/ISO_3166-1) standard.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n", "responses": { "200": { "description": "Image" @@ -4185,7 +4185,7 @@ "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.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 400x400px.\n\nThis endpoint does not follow HTTP redirects.", + "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.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 400x400px.\r\n\r\nThis endpoint does not follow HTTP redirects.", "responses": { "200": { "description": "Image" @@ -4269,7 +4269,7 @@ "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.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n", + "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.\r\n\r\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.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n", "responses": { "200": { "description": "Image" @@ -4363,7 +4363,7 @@ "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.\n", + "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.\r\n", "responses": { "200": { "description": "Image" @@ -4644,7 +4644,7 @@ "tags": [ "databases" ], - "description": "Create a new Database.\n", + "description": "Create a new Database.\r\n", "responses": { "201": { "description": "Database", @@ -5523,7 +5523,7 @@ "tags": [ "databases" ], - "description": "Create a boolean attribute.\n", + "description": "Create a boolean attribute.\r\n", "responses": { "202": { "description": "AttributeBoolean", @@ -5965,7 +5965,7 @@ "tags": [ "databases" ], - "description": "Create an email attribute.\n", + "description": "Create an email attribute.\r\n", "responses": { "202": { "description": "AttributeEmail", @@ -6073,7 +6073,7 @@ "tags": [ "databases" ], - "description": "Update an email attribute. Changing the `default` value will not update already existing documents.\n", + "description": "Update an email attribute. Changing the `default` value will not update already existing documents.\r\n", "responses": { "200": { "description": "AttributeEmail", @@ -6186,7 +6186,7 @@ "tags": [ "databases" ], - "description": "Create an enumeration attribute. The `elements` param acts as a white-list of accepted values for this attribute. \n", + "description": "Create an enumeration attribute. The `elements` param acts as a white-list of accepted values for this attribute. \r\n", "responses": { "202": { "description": "AttributeEnum", @@ -6303,7 +6303,7 @@ "tags": [ "databases" ], - "description": "Update an enum attribute. Changing the `default` value will not update already existing documents.\n", + "description": "Update an enum attribute. Changing the `default` value will not update already existing documents.\r\n", "responses": { "200": { "description": "AttributeEnum", @@ -6425,7 +6425,7 @@ "tags": [ "databases" ], - "description": "Create a float attribute. Optionally, minimum and maximum values can be provided.\n", + "description": "Create a float attribute. Optionally, minimum and maximum values can be provided.\r\n", "responses": { "202": { "description": "AttributeFloat", @@ -6543,7 +6543,7 @@ "tags": [ "databases" ], - "description": "Update a float attribute. Changing the `default` value will not update already existing documents.\n", + "description": "Update a float attribute. Changing the `default` value will not update already existing documents.\r\n", "responses": { "200": { "description": "AttributeFloat", @@ -6668,7 +6668,7 @@ "tags": [ "databases" ], - "description": "Create an integer attribute. Optionally, minimum and maximum values can be provided.\n", + "description": "Create an integer attribute. Optionally, minimum and maximum values can be provided.\r\n", "responses": { "202": { "description": "AttributeInteger", @@ -6786,7 +6786,7 @@ "tags": [ "databases" ], - "description": "Update an integer attribute. Changing the `default` value will not update already existing documents.\n", + "description": "Update an integer attribute. Changing the `default` value will not update already existing documents.\r\n", "responses": { "200": { "description": "AttributeInteger", @@ -6911,7 +6911,7 @@ "tags": [ "databases" ], - "description": "Create IP address attribute.\n", + "description": "Create IP address attribute.\r\n", "responses": { "202": { "description": "AttributeIP", @@ -7019,7 +7019,7 @@ "tags": [ "databases" ], - "description": "Update an ip attribute. Changing the `default` value will not update already existing documents.\n", + "description": "Update an ip attribute. Changing the `default` value will not update already existing documents.\r\n", "responses": { "200": { "description": "AttributeIP", @@ -7132,7 +7132,7 @@ "tags": [ "databases" ], - "description": "Create relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\n", + "description": "Create relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\r\n", "responses": { "202": { "description": "AttributeRelationship", @@ -7265,7 +7265,7 @@ "tags": [ "databases" ], - "description": "Create a string attribute.\n", + "description": "Create a string attribute.\r\n", "responses": { "202": { "description": "AttributeString", @@ -7384,7 +7384,7 @@ "tags": [ "databases" ], - "description": "Update a string attribute. Changing the `default` value will not update already existing documents.\n", + "description": "Update a string attribute. Changing the `default` value will not update already existing documents.\r\n", "responses": { "200": { "description": "AttributeString", @@ -7502,7 +7502,7 @@ "tags": [ "databases" ], - "description": "Create a URL attribute.\n", + "description": "Create a URL attribute.\r\n", "responses": { "202": { "description": "AttributeURL", @@ -7610,7 +7610,7 @@ "tags": [ "databases" ], - "description": "Update an url attribute. Changing the `default` value will not update already existing documents.\n", + "description": "Update an url attribute. Changing the `default` value will not update already existing documents.\r\n", "responses": { "200": { "description": "AttributeURL", @@ -7909,7 +7909,7 @@ "tags": [ "databases" ], - "description": "Update relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\n", + "description": "Update relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\r\n", "responses": { "200": { "description": "AttributeRelationship", @@ -8678,7 +8678,7 @@ "tags": [ "databases" ], - "description": "Creates an index on the attributes listed. Your index should include all the attributes you will query in a single request.\nAttributes can be `key`, `fulltext`, and `unique`.", + "description": "Creates an index on the attributes listed. Your index should include all the attributes you will query in a single request.\r\nAttributes can be `key`, `fulltext`, and `unique`.", "responses": { "202": { "description": "Index", @@ -9658,7 +9658,7 @@ "tags": [ "functions" ], - "description": "List allowed function specifications for this instance.\n", + "description": "List allowed function specifications for this instance.\r\n", "responses": { "200": { "description": "Specifications List", @@ -10373,7 +10373,7 @@ "tags": [ "functions" ], - "description": "Create a new function code deployment. 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 deployment to use your new deployment 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](https:\/\/appwrite.io\/docs\/functions).\n\nUse the \"command\" param to set the entrypoint used to execute your code.", + "description": "Create a new function code deployment. 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 deployment to use your new deployment UID.\r\n\r\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](https:\/\/appwrite.io\/docs\/functions).\r\n\r\nUse the \"command\" param to set the entrypoint used to execute your code.", "responses": { "202": { "description": "Deployment", @@ -11179,7 +11179,7 @@ "tags": [ "functions" ], - "description": "Delete a function execution by its unique ID.\n", + "description": "Delete a function execution by its unique ID.\r\n", "responses": { "204": { "description": "No content" @@ -12453,7 +12453,7 @@ "tags": [ "health" ], - "description": "Returns the amount of failed jobs in a given queue.\n", + "description": "Returns the amount of failed jobs in a given queue.\r\n", "responses": { "200": { "description": "Health Queue", @@ -13208,7 +13208,7 @@ "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))", + "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.\r\n\r\n([IP Geolocation by DB-IP](https:\/\/db-ip.com))", "responses": { "200": { "description": "Locale", @@ -13864,7 +13864,7 @@ "tags": [ "messaging" ], - "description": "Update an email message by its unique ID.\n", + "description": "Update an email message by its unique ID.\r\n", "responses": { "200": { "description": "Message", @@ -14169,7 +14169,7 @@ "tags": [ "messaging" ], - "description": "Update a push notification by its unique ID.\n", + "description": "Update a push notification by its unique ID.\r\n", "responses": { "200": { "description": "Message", @@ -14439,7 +14439,7 @@ "tags": [ "messaging" ], - "description": "Update an email message by its unique ID.\n", + "description": "Update an email message by its unique ID.\r\n", "responses": { "200": { "description": "Message", @@ -14553,7 +14553,7 @@ "tags": [ "messaging" ], - "description": "Get a message by its unique ID.\n", + "description": "Get a message by its unique ID.\r\n", "responses": { "200": { "description": "Message", @@ -17029,7 +17029,7 @@ "tags": [ "messaging" ], - "description": "Get a provider by its unique ID.\n", + "description": "Get a provider by its unique ID.\r\n", "responses": { "200": { "description": "Provider", @@ -17463,7 +17463,7 @@ "tags": [ "messaging" ], - "description": "Get a topic by its unique ID.\n", + "description": "Get a topic by its unique ID.\r\n", "responses": { "200": { "description": "Topic", @@ -17525,7 +17525,7 @@ "tags": [ "messaging" ], - "description": "Update a topic by its unique ID.\n", + "description": "Update a topic by its unique ID.\r\n", "responses": { "200": { "description": "Topic", @@ -17923,7 +17923,7 @@ "tags": [ "messaging" ], - "description": "Get a subscriber by its unique ID.\n", + "description": "Get a subscriber by its unique ID.\r\n", "responses": { "200": { "description": "Subscriber", @@ -22620,7 +22620,8 @@ "description": "Does SMTP server use secure connection", "x-example": "tls", "enum": [ - "tls" + "tls", + "ssl" ], "x-enum-name": "SMTPSecure", "x-enum-keys": [] @@ -25524,7 +25525,7 @@ "tags": [ "storage" ], - "description": "Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/storage#storageCreateBucket) API or directly from your Appwrite console.\n\nLarger files should be uploaded using multiple requests with the [content-range](https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Content-Range) header to send a partial request with a maximum supported chunk of `5MB`. The `content-range` header values should always be in bytes.\n\nWhen the first request is sent, the server will return the **File** object, and the subsequent part request must include the file's **id** in `x-appwrite-id` header to allow the server to know that the partial upload is for the existing file and not for a new one.\n\nIf you're creating a new file using one of the Appwrite SDKs, all the chunking logic will be managed by the SDK internally.\n", + "description": "Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/storage#storageCreateBucket) API or directly from your Appwrite console.\r\n\r\nLarger files should be uploaded using multiple requests with the [content-range](https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Content-Range) header to send a partial request with a maximum supported chunk of `5MB`. The `content-range` header values should always be in bytes.\r\n\r\nWhen the first request is sent, the server will return the **File** object, and the subsequent part request must include the file's **id** in `x-appwrite-id` header to allow the server to know that the partial upload is for the existing file and not for a new one.\r\n\r\nIf you're creating a new file using one of the Appwrite SDKs, all the chunking logic will be managed by the SDK internally.\r\n", "responses": { "201": { "description": "File", @@ -26133,7 +26134,8 @@ "jpeg", "gif", "png", - "webp" + "webp", + "avif" ], "x-enum-name": "ImageFormat", "x-enum-keys": [], @@ -26912,7 +26914,7 @@ "tags": [ "teams" ], - "description": "Invite a new member to join your team. Provide an ID for existing users, or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team.\n\nYou only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters.\n\nUse the `url` parameter to redirect the user from the invitation email to your app. After the user is redirected, use the [Update Team Membership Status](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team. \n\nPlease note that to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console.\n", + "description": "Invite a new member to join your team. Provide an ID for existing users, or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team.\r\n\r\nYou only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters.\r\n\r\nUse the `url` parameter to redirect the user from the invitation email to your app. After the user is redirected, use the [Update Team Membership Status](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team. \r\n\r\nPlease note that to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console.\r\n", "responses": { "201": { "description": "Membership", @@ -27099,7 +27101,7 @@ "tags": [ "teams" ], - "description": "Modify the roles of a team member. Only team members with the owner role have access to this endpoint. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions).\n", + "description": "Modify the roles of a team member. Only team members with the owner role have access to this endpoint. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions).\r\n", "responses": { "200": { "description": "Membership", @@ -27264,7 +27266,7 @@ "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 received by the user.\n\nIf the request is successful, a session for the user is automatically created.\n", + "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 received by the user.\r\n\r\nIf the request is successful, a session for the user is automatically created.\r\n", "responses": { "200": { "description": "Membership", @@ -28840,7 +28842,7 @@ "tags": [ "users" ], - "description": "Update the user labels by its unique ID. \n\nLabels can be used to grant access to resources. While teams are a way for user's to share access to a resource, labels can be defined by the developer to grant access without an invitation. See the [Permissions docs](https:\/\/appwrite.io\/docs\/permissions) for more info.", + "description": "Update the user labels by its unique ID. \r\n\r\nLabels can be used to grant access to resources. While teams are a way for user's to share access to a resource, labels can be defined by the developer to grant access without an invitation. See the [Permissions docs](https:\/\/appwrite.io\/docs\/permissions) for more info.", "responses": { "200": { "description": "User", @@ -29922,7 +29924,7 @@ "tags": [ "users" ], - "description": "Creates a session for a user. Returns an immediately usable session object.\n\nIf you want to generate a token for a custom authentication flow, use the [POST \/users\/{userId}\/tokens](https:\/\/appwrite.io\/docs\/server\/users#createToken) endpoint.", + "description": "Creates a session for a user. Returns an immediately usable session object.\r\n\r\nIf you want to generate a token for a custom authentication flow, use the [POST \/users\/{userId}\/tokens](https:\/\/appwrite.io\/docs\/server\/users#createToken) endpoint.", "responses": { "201": { "description": "Session", @@ -30611,7 +30613,7 @@ "tags": [ "users" ], - "description": "Returns a token with a secret key for creating a session. Use the user ID and secret and submit a request to the [PUT \/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process.\n", + "description": "Returns a token with a secret key for creating a session. Use the user ID and secret and submit a request to the [PUT \/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process.\r\n", "responses": { "201": { "description": "Token", diff --git a/app/config/specs/open-api3-1.6.x-server.json b/app/config/specs/open-api3-1.6.x-server.json index 1a394f6def..47fe17866a 100644 --- a/app/config/specs/open-api3-1.6.x-server.json +++ b/app/config/specs/open-api3-1.6.x-server.json @@ -167,7 +167,7 @@ "tags": [ "account" ], - "description": "Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. 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.\n", + "description": "Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. For security measures, user password is required to complete this request.\r\nThis endpoint can also be used to convert an anonymous account to a normal one, by passing an email address and a new password.\r\n", "responses": { "200": { "description": "User", @@ -1590,7 +1590,7 @@ "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](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createRecovery) 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.", + "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](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createRecovery) endpoint.\r\n\r\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", @@ -1825,7 +1825,7 @@ "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.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", + "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.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", "responses": { "201": { "description": "Session", @@ -2370,7 +2370,7 @@ "tags": [ "account" ], - "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's email is valid for 15 minutes.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", + "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's email is valid for 15 minutes.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", "responses": { "201": { "description": "Token", @@ -2451,7 +2451,7 @@ "tags": [ "account" ], - "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. If you are on a mobile device you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\n", + "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. If you are on a mobile device you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\r\n", "responses": { "201": { "description": "Token", @@ -2540,7 +2540,7 @@ "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. \n\nIf authentication succeeds, `userId` and `secret` of a token will be appended to the success URL as query parameters. These can be used to create a new session using the [Create session](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", + "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. \r\n\r\nIf authentication succeeds, `userId` and `secret` of a token will be appended to the success URL as query parameters. These can be used to create a new session using the [Create session](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", "responses": { "301": { "description": "File" @@ -2676,7 +2676,7 @@ "tags": [ "account" ], - "description": "Sends the user an SMS with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's phone is valid for 15 minutes.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", + "description": "Sends the user an SMS with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's phone is valid for 15 minutes.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", "responses": { "201": { "description": "Token", @@ -2755,7 +2755,7 @@ "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](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). 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", + "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](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\r\n\r\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.\r\n", "responses": { "201": { "description": "Token", @@ -3039,7 +3039,7 @@ "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 [GET \/account\/sessions](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#getSessions) endpoint. Use width, height and quality arguments to change the output settings.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.", + "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 [GET \/account\/sessions](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#getSessions) endpoint. Use width, height and quality arguments to change the output settings.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.", "responses": { "200": { "description": "Image" @@ -3169,7 +3169,7 @@ "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.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n", + "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.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n", "responses": { "200": { "description": "Image" @@ -3303,7 +3303,7 @@ "tags": [ "avatars" ], - "description": "Use this endpoint to fetch the favorite icon (AKA favicon) of any remote website URL.\n\nThis endpoint does not follow HTTP redirects.", + "description": "Use this endpoint to fetch the favorite icon (AKA favicon) of any remote website URL.\r\n\r\nThis endpoint does not follow HTTP redirects.", "responses": { "200": { "description": "Image" @@ -3365,7 +3365,7 @@ "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. Country codes follow the [ISO 3166-1](https:\/\/en.wikipedia.org\/wiki\/ISO_3166-1) standard.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n", + "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. Country codes follow the [ISO 3166-1](https:\/\/en.wikipedia.org\/wiki\/ISO_3166-1) standard.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n", "responses": { "200": { "description": "Image" @@ -3857,7 +3857,7 @@ "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.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 400x400px.\n\nThis endpoint does not follow HTTP redirects.", + "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.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 400x400px.\r\n\r\nThis endpoint does not follow HTTP redirects.", "responses": { "200": { "description": "Image" @@ -3943,7 +3943,7 @@ "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.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n", + "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.\r\n\r\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.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n", "responses": { "200": { "description": "Image" @@ -4039,7 +4039,7 @@ "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.\n", + "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.\r\n", "responses": { "200": { "description": "Image" @@ -4211,7 +4211,7 @@ "tags": [ "databases" ], - "description": "Create a new Database.\n", + "description": "Create a new Database.\r\n", "responses": { "201": { "description": "Database", @@ -5026,7 +5026,7 @@ "tags": [ "databases" ], - "description": "Create a boolean attribute.\n", + "description": "Create a boolean attribute.\r\n", "responses": { "202": { "description": "AttributeBoolean", @@ -5472,7 +5472,7 @@ "tags": [ "databases" ], - "description": "Create an email attribute.\n", + "description": "Create an email attribute.\r\n", "responses": { "202": { "description": "AttributeEmail", @@ -5581,7 +5581,7 @@ "tags": [ "databases" ], - "description": "Update an email attribute. Changing the `default` value will not update already existing documents.\n", + "description": "Update an email attribute. Changing the `default` value will not update already existing documents.\r\n", "responses": { "200": { "description": "AttributeEmail", @@ -5695,7 +5695,7 @@ "tags": [ "databases" ], - "description": "Create an enumeration attribute. The `elements` param acts as a white-list of accepted values for this attribute. \n", + "description": "Create an enumeration attribute. The `elements` param acts as a white-list of accepted values for this attribute. \r\n", "responses": { "202": { "description": "AttributeEnum", @@ -5813,7 +5813,7 @@ "tags": [ "databases" ], - "description": "Update an enum attribute. Changing the `default` value will not update already existing documents.\n", + "description": "Update an enum attribute. Changing the `default` value will not update already existing documents.\r\n", "responses": { "200": { "description": "AttributeEnum", @@ -5936,7 +5936,7 @@ "tags": [ "databases" ], - "description": "Create a float attribute. Optionally, minimum and maximum values can be provided.\n", + "description": "Create a float attribute. Optionally, minimum and maximum values can be provided.\r\n", "responses": { "202": { "description": "AttributeFloat", @@ -6055,7 +6055,7 @@ "tags": [ "databases" ], - "description": "Update a float attribute. Changing the `default` value will not update already existing documents.\n", + "description": "Update a float attribute. Changing the `default` value will not update already existing documents.\r\n", "responses": { "200": { "description": "AttributeFloat", @@ -6181,7 +6181,7 @@ "tags": [ "databases" ], - "description": "Create an integer attribute. Optionally, minimum and maximum values can be provided.\n", + "description": "Create an integer attribute. Optionally, minimum and maximum values can be provided.\r\n", "responses": { "202": { "description": "AttributeInteger", @@ -6300,7 +6300,7 @@ "tags": [ "databases" ], - "description": "Update an integer attribute. Changing the `default` value will not update already existing documents.\n", + "description": "Update an integer attribute. Changing the `default` value will not update already existing documents.\r\n", "responses": { "200": { "description": "AttributeInteger", @@ -6426,7 +6426,7 @@ "tags": [ "databases" ], - "description": "Create IP address attribute.\n", + "description": "Create IP address attribute.\r\n", "responses": { "202": { "description": "AttributeIP", @@ -6535,7 +6535,7 @@ "tags": [ "databases" ], - "description": "Update an ip attribute. Changing the `default` value will not update already existing documents.\n", + "description": "Update an ip attribute. Changing the `default` value will not update already existing documents.\r\n", "responses": { "200": { "description": "AttributeIP", @@ -6649,7 +6649,7 @@ "tags": [ "databases" ], - "description": "Create relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\n", + "description": "Create relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\r\n", "responses": { "202": { "description": "AttributeRelationship", @@ -6783,7 +6783,7 @@ "tags": [ "databases" ], - "description": "Create a string attribute.\n", + "description": "Create a string attribute.\r\n", "responses": { "202": { "description": "AttributeString", @@ -6903,7 +6903,7 @@ "tags": [ "databases" ], - "description": "Update a string attribute. Changing the `default` value will not update already existing documents.\n", + "description": "Update a string attribute. Changing the `default` value will not update already existing documents.\r\n", "responses": { "200": { "description": "AttributeString", @@ -7022,7 +7022,7 @@ "tags": [ "databases" ], - "description": "Create a URL attribute.\n", + "description": "Create a URL attribute.\r\n", "responses": { "202": { "description": "AttributeURL", @@ -7131,7 +7131,7 @@ "tags": [ "databases" ], - "description": "Update an url attribute. Changing the `default` value will not update already existing documents.\n", + "description": "Update an url attribute. Changing the `default` value will not update already existing documents.\r\n", "responses": { "200": { "description": "AttributeURL", @@ -7433,7 +7433,7 @@ "tags": [ "databases" ], - "description": "Update relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\n", + "description": "Update relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\r\n", "responses": { "200": { "description": "AttributeRelationship", @@ -8119,7 +8119,7 @@ "tags": [ "databases" ], - "description": "Creates an index on the attributes listed. Your index should include all the attributes you will query in a single request.\nAttributes can be `key`, `fulltext`, and `unique`.", + "description": "Creates an index on the attributes listed. Your index should include all the attributes you will query in a single request.\r\nAttributes can be `key`, `fulltext`, and `unique`.", "responses": { "202": { "description": "Index", @@ -8767,7 +8767,7 @@ "tags": [ "functions" ], - "description": "List allowed function specifications for this instance.\n", + "description": "List allowed function specifications for this instance.\r\n", "responses": { "200": { "description": "Specifications List", @@ -9249,7 +9249,7 @@ "tags": [ "functions" ], - "description": "Create a new function code deployment. 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 deployment to use your new deployment 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](https:\/\/appwrite.io\/docs\/functions).\n\nUse the \"command\" param to set the entrypoint used to execute your code.", + "description": "Create a new function code deployment. 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 deployment to use your new deployment UID.\r\n\r\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](https:\/\/appwrite.io\/docs\/functions).\r\n\r\nUse the \"command\" param to set the entrypoint used to execute your code.", "responses": { "202": { "description": "Deployment", @@ -10068,7 +10068,7 @@ "tags": [ "functions" ], - "description": "Delete a function execution by its unique ID.\n", + "description": "Delete a function execution by its unique ID.\r\n", "responses": { "204": { "description": "No content" @@ -11279,7 +11279,7 @@ "tags": [ "health" ], - "description": "Returns the amount of failed jobs in a given queue.\n", + "description": "Returns the amount of failed jobs in a given queue.\r\n", "responses": { "200": { "description": "Health Queue", @@ -12046,7 +12046,7 @@ "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))", + "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.\r\n\r\n([IP Geolocation by DB-IP](https:\/\/db-ip.com))", "responses": { "200": { "description": "Locale", @@ -12720,7 +12720,7 @@ "tags": [ "messaging" ], - "description": "Update an email message by its unique ID.\n", + "description": "Update an email message by its unique ID.\r\n", "responses": { "200": { "description": "Message", @@ -13027,7 +13027,7 @@ "tags": [ "messaging" ], - "description": "Update a push notification by its unique ID.\n", + "description": "Update a push notification by its unique ID.\r\n", "responses": { "200": { "description": "Message", @@ -13299,7 +13299,7 @@ "tags": [ "messaging" ], - "description": "Update an email message by its unique ID.\n", + "description": "Update an email message by its unique ID.\r\n", "responses": { "200": { "description": "Message", @@ -13414,7 +13414,7 @@ "tags": [ "messaging" ], - "description": "Get a message by its unique ID.\n", + "description": "Get a message by its unique ID.\r\n", "responses": { "200": { "description": "Message", @@ -15915,7 +15915,7 @@ "tags": [ "messaging" ], - "description": "Get a provider by its unique ID.\n", + "description": "Get a provider by its unique ID.\r\n", "responses": { "200": { "description": "Provider", @@ -16355,7 +16355,7 @@ "tags": [ "messaging" ], - "description": "Get a topic by its unique ID.\n", + "description": "Get a topic by its unique ID.\r\n", "responses": { "200": { "description": "Topic", @@ -16418,7 +16418,7 @@ "tags": [ "messaging" ], - "description": "Update a topic by its unique ID.\n", + "description": "Update a topic by its unique ID.\r\n", "responses": { "200": { "description": "Topic", @@ -16822,7 +16822,7 @@ "tags": [ "messaging" ], - "description": "Get a subscriber by its unique ID.\n", + "description": "Get a subscriber by its unique ID.\r\n", "responses": { "200": { "description": "Subscriber", @@ -17516,7 +17516,7 @@ "tags": [ "storage" ], - "description": "Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/storage#storageCreateBucket) API or directly from your Appwrite console.\n\nLarger files should be uploaded using multiple requests with the [content-range](https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Content-Range) header to send a partial request with a maximum supported chunk of `5MB`. The `content-range` header values should always be in bytes.\n\nWhen the first request is sent, the server will return the **File** object, and the subsequent part request must include the file's **id** in `x-appwrite-id` header to allow the server to know that the partial upload is for the existing file and not for a new one.\n\nIf you're creating a new file using one of the Appwrite SDKs, all the chunking logic will be managed by the SDK internally.\n", + "description": "Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/storage#storageCreateBucket) API or directly from your Appwrite console.\r\n\r\nLarger files should be uploaded using multiple requests with the [content-range](https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Content-Range) header to send a partial request with a maximum supported chunk of `5MB`. The `content-range` header values should always be in bytes.\r\n\r\nWhen the first request is sent, the server will return the **File** object, and the subsequent part request must include the file's **id** in `x-appwrite-id` header to allow the server to know that the partial upload is for the existing file and not for a new one.\r\n\r\nIf you're creating a new file using one of the Appwrite SDKs, all the chunking logic will be managed by the SDK internally.\r\n", "responses": { "201": { "description": "File", @@ -18137,7 +18137,8 @@ "jpeg", "gif", "png", - "webp" + "webp", + "avif" ], "x-enum-name": "ImageFormat", "x-enum-keys": [], @@ -18697,7 +18698,7 @@ "tags": [ "teams" ], - "description": "Invite a new member to join your team. Provide an ID for existing users, or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team.\n\nYou only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters.\n\nUse the `url` parameter to redirect the user from the invitation email to your app. After the user is redirected, use the [Update Team Membership Status](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team. \n\nPlease note that to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console.\n", + "description": "Invite a new member to join your team. Provide an ID for existing users, or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team.\r\n\r\nYou only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters.\r\n\r\nUse the `url` parameter to redirect the user from the invitation email to your app. After the user is redirected, use the [Update Team Membership Status](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team. \r\n\r\nPlease note that to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console.\r\n", "responses": { "201": { "description": "Membership", @@ -18888,7 +18889,7 @@ "tags": [ "teams" ], - "description": "Modify the roles of a team member. Only team members with the owner role have access to this endpoint. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions).\n", + "description": "Modify the roles of a team member. Only team members with the owner role have access to this endpoint. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions).\r\n", "responses": { "200": { "description": "Membership", @@ -19057,7 +19058,7 @@ "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 received by the user.\n\nIf the request is successful, a session for the user is automatically created.\n", + "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 received by the user.\r\n\r\nIf the request is successful, a session for the user is automatically created.\r\n", "responses": { "200": { "description": "Membership", @@ -20580,7 +20581,7 @@ "tags": [ "users" ], - "description": "Update the user labels by its unique ID. \n\nLabels can be used to grant access to resources. While teams are a way for user's to share access to a resource, labels can be defined by the developer to grant access without an invitation. See the [Permissions docs](https:\/\/appwrite.io\/docs\/permissions) for more info.", + "description": "Update the user labels by its unique ID. \r\n\r\nLabels can be used to grant access to resources. While teams are a way for user's to share access to a resource, labels can be defined by the developer to grant access without an invitation. See the [Permissions docs](https:\/\/appwrite.io\/docs\/permissions) for more info.", "responses": { "200": { "description": "User", @@ -21677,7 +21678,7 @@ "tags": [ "users" ], - "description": "Creates a session for a user. Returns an immediately usable session object.\n\nIf you want to generate a token for a custom authentication flow, use the [POST \/users\/{userId}\/tokens](https:\/\/appwrite.io\/docs\/server\/users#createToken) endpoint.", + "description": "Creates a session for a user. Returns an immediately usable session object.\r\n\r\nIf you want to generate a token for a custom authentication flow, use the [POST \/users\/{userId}\/tokens](https:\/\/appwrite.io\/docs\/server\/users#createToken) endpoint.", "responses": { "201": { "description": "Session", @@ -22375,7 +22376,7 @@ "tags": [ "users" ], - "description": "Returns a token with a secret key for creating a session. Use the user ID and secret and submit a request to the [PUT \/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process.\n", + "description": "Returns a token with a secret key for creating a session. Use the user ID and secret and submit a request to the [PUT \/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process.\r\n", "responses": { "201": { "description": "Token", diff --git a/app/config/specs/open-api3-latest-client.json b/app/config/specs/open-api3-latest-client.json index f1b247a70f..021ae27c45 100644 --- a/app/config/specs/open-api3-latest-client.json +++ b/app/config/specs/open-api3-latest-client.json @@ -166,7 +166,7 @@ "tags": [ "account" ], - "description": "Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. 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.\n", + "description": "Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. For security measures, user password is required to complete this request.\r\nThis endpoint can also be used to convert an anonymous account to a normal one, by passing an email address and a new password.\r\n", "responses": { "200": { "description": "User", @@ -1570,7 +1570,7 @@ "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](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createRecovery) 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.", + "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](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createRecovery) endpoint.\r\n\r\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", @@ -1802,7 +1802,7 @@ "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.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", + "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.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", "responses": { "201": { "description": "Session", @@ -1954,7 +1954,7 @@ "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.\n\nIf there is already an active session, the new session will be attached to the logged-in account. If there are no active sessions, the server will attempt to look for a user with the same email address as the email received from the OAuth2 provider and attach the new session to the existing user. If no matching user is found - the server will create a new user.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\n", + "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.\r\n\r\nIf there is already an active session, the new session will be attached to the logged-in account. If there are no active sessions, the server will attempt to look for a user with the same email address as the email received from the OAuth2 provider and attach the new session to the existing user. If no matching user is found - the server will create a new user.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\r\n", "responses": { "301": { "description": "File" @@ -2703,7 +2703,7 @@ "tags": [ "account" ], - "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's email is valid for 15 minutes.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", + "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's email is valid for 15 minutes.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", "responses": { "201": { "description": "Token", @@ -2784,7 +2784,7 @@ "tags": [ "account" ], - "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. If you are on a mobile device you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\n", + "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. If you are on a mobile device you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\r\n", "responses": { "201": { "description": "Token", @@ -2873,7 +2873,7 @@ "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. \n\nIf authentication succeeds, `userId` and `secret` of a token will be appended to the success URL as query parameters. These can be used to create a new session using the [Create session](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", + "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. \r\n\r\nIf authentication succeeds, `userId` and `secret` of a token will be appended to the success URL as query parameters. These can be used to create a new session using the [Create session](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", "responses": { "301": { "description": "File" @@ -3009,7 +3009,7 @@ "tags": [ "account" ], - "description": "Sends the user an SMS with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's phone is valid for 15 minutes.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", + "description": "Sends the user an SMS with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's phone is valid for 15 minutes.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", "responses": { "201": { "description": "Token", @@ -3088,7 +3088,7 @@ "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](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). 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", + "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](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\r\n\r\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.\r\n", "responses": { "201": { "description": "Token", @@ -3368,7 +3368,7 @@ "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 [GET \/account\/sessions](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#getSessions) endpoint. Use width, height and quality arguments to change the output settings.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.", + "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 [GET \/account\/sessions](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#getSessions) endpoint. Use width, height and quality arguments to change the output settings.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.", "responses": { "200": { "description": "Image" @@ -3496,7 +3496,7 @@ "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.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n", + "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.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n", "responses": { "200": { "description": "Image" @@ -3628,7 +3628,7 @@ "tags": [ "avatars" ], - "description": "Use this endpoint to fetch the favorite icon (AKA favicon) of any remote website URL.\n\nThis endpoint does not follow HTTP redirects.", + "description": "Use this endpoint to fetch the favorite icon (AKA favicon) of any remote website URL.\r\n\r\nThis endpoint does not follow HTTP redirects.", "responses": { "200": { "description": "Image" @@ -3688,7 +3688,7 @@ "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. Country codes follow the [ISO 3166-1](https:\/\/en.wikipedia.org\/wiki\/ISO_3166-1) standard.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n", + "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. Country codes follow the [ISO 3166-1](https:\/\/en.wikipedia.org\/wiki\/ISO_3166-1) standard.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n", "responses": { "200": { "description": "Image" @@ -4178,7 +4178,7 @@ "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.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 400x400px.\n\nThis endpoint does not follow HTTP redirects.", + "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.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 400x400px.\r\n\r\nThis endpoint does not follow HTTP redirects.", "responses": { "200": { "description": "Image" @@ -4262,7 +4262,7 @@ "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.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n", + "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.\r\n\r\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.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n", "responses": { "200": { "description": "Image" @@ -4356,7 +4356,7 @@ "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.\n", + "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.\r\n", "responses": { "200": { "description": "Image" @@ -5319,7 +5319,7 @@ "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))", + "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.\r\n\r\n([IP Geolocation by DB-IP](https:\/\/db-ip.com))", "responses": { "200": { "description": "Locale", @@ -6001,7 +6001,7 @@ "tags": [ "storage" ], - "description": "Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/storage#storageCreateBucket) API or directly from your Appwrite console.\n\nLarger files should be uploaded using multiple requests with the [content-range](https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Content-Range) header to send a partial request with a maximum supported chunk of `5MB`. The `content-range` header values should always be in bytes.\n\nWhen the first request is sent, the server will return the **File** object, and the subsequent part request must include the file's **id** in `x-appwrite-id` header to allow the server to know that the partial upload is for the existing file and not for a new one.\n\nIf you're creating a new file using one of the Appwrite SDKs, all the chunking logic will be managed by the SDK internally.\n", + "description": "Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/storage#storageCreateBucket) API or directly from your Appwrite console.\r\n\r\nLarger files should be uploaded using multiple requests with the [content-range](https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Content-Range) header to send a partial request with a maximum supported chunk of `5MB`. The `content-range` header values should always be in bytes.\r\n\r\nWhen the first request is sent, the server will return the **File** object, and the subsequent part request must include the file's **id** in `x-appwrite-id` header to allow the server to know that the partial upload is for the existing file and not for a new one.\r\n\r\nIf you're creating a new file using one of the Appwrite SDKs, all the chunking logic will be managed by the SDK internally.\r\n", "responses": { "201": { "description": "File", @@ -6610,7 +6610,8 @@ "jpeg", "gif", "png", - "webp" + "webp", + "avif" ], "x-enum-name": "ImageFormat", "x-enum-keys": [], @@ -7156,7 +7157,7 @@ "tags": [ "teams" ], - "description": "Invite a new member to join your team. Provide an ID for existing users, or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team.\n\nYou only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters.\n\nUse the `url` parameter to redirect the user from the invitation email to your app. After the user is redirected, use the [Update Team Membership Status](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team. \n\nPlease note that to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console.\n", + "description": "Invite a new member to join your team. Provide an ID for existing users, or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team.\r\n\r\nYou only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters.\r\n\r\nUse the `url` parameter to redirect the user from the invitation email to your app. After the user is redirected, use the [Update Team Membership Status](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team. \r\n\r\nPlease note that to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console.\r\n", "responses": { "201": { "description": "Membership", @@ -7343,7 +7344,7 @@ "tags": [ "teams" ], - "description": "Modify the roles of a team member. Only team members with the owner role have access to this endpoint. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions).\n", + "description": "Modify the roles of a team member. Only team members with the owner role have access to this endpoint. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions).\r\n", "responses": { "200": { "description": "Membership", @@ -7508,7 +7509,7 @@ "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 received by the user.\n\nIf the request is successful, a session for the user is automatically created.\n", + "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 received by the user.\r\n\r\nIf the request is successful, a session for the user is automatically created.\r\n", "responses": { "200": { "description": "Membership", diff --git a/app/config/specs/open-api3-latest-console.json b/app/config/specs/open-api3-latest-console.json index 1e3c1a21c4..12163dd464 100644 --- a/app/config/specs/open-api3-latest-console.json +++ b/app/config/specs/open-api3-latest-console.json @@ -206,7 +206,7 @@ "tags": [ "account" ], - "description": "Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. 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.\n", + "description": "Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. For security measures, user password is required to complete this request.\r\nThis endpoint can also be used to convert an anonymous account to a normal one, by passing an email address and a new password.\r\n", "responses": { "200": { "description": "User", @@ -1591,7 +1591,7 @@ "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](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createRecovery) 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.", + "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](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createRecovery) endpoint.\r\n\r\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", @@ -1820,7 +1820,7 @@ "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.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", + "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.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", "responses": { "201": { "description": "Session", @@ -1972,7 +1972,7 @@ "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.\n\nIf there is already an active session, the new session will be attached to the logged-in account. If there are no active sessions, the server will attempt to look for a user with the same email address as the email received from the OAuth2 provider and attach the new session to the existing user. If no matching user is found - the server will create a new user.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\n", + "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.\r\n\r\nIf there is already an active session, the new session will be attached to the logged-in account. If there are no active sessions, the server will attempt to look for a user with the same email address as the email received from the OAuth2 provider and attach the new session to the existing user. If no matching user is found - the server will create a new user.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\r\n", "responses": { "301": { "description": "File" @@ -2714,7 +2714,7 @@ "tags": [ "account" ], - "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's email is valid for 15 minutes.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", + "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's email is valid for 15 minutes.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", "responses": { "201": { "description": "Token", @@ -2795,7 +2795,7 @@ "tags": [ "account" ], - "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. If you are on a mobile device you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\n", + "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. If you are on a mobile device you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\r\n", "responses": { "201": { "description": "Token", @@ -2884,7 +2884,7 @@ "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. \n\nIf authentication succeeds, `userId` and `secret` of a token will be appended to the success URL as query parameters. These can be used to create a new session using the [Create session](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", + "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. \r\n\r\nIf authentication succeeds, `userId` and `secret` of a token will be appended to the success URL as query parameters. These can be used to create a new session using the [Create session](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", "responses": { "301": { "description": "File" @@ -3020,7 +3020,7 @@ "tags": [ "account" ], - "description": "Sends the user an SMS with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's phone is valid for 15 minutes.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", + "description": "Sends the user an SMS with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's phone is valid for 15 minutes.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", "responses": { "201": { "description": "Token", @@ -3099,7 +3099,7 @@ "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](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). 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", + "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](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\r\n\r\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.\r\n", "responses": { "201": { "description": "Token", @@ -3375,7 +3375,7 @@ "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 [GET \/account\/sessions](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#getSessions) endpoint. Use width, height and quality arguments to change the output settings.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.", + "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 [GET \/account\/sessions](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#getSessions) endpoint. Use width, height and quality arguments to change the output settings.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.", "responses": { "200": { "description": "Image" @@ -3503,7 +3503,7 @@ "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.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n", + "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.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n", "responses": { "200": { "description": "Image" @@ -3635,7 +3635,7 @@ "tags": [ "avatars" ], - "description": "Use this endpoint to fetch the favorite icon (AKA favicon) of any remote website URL.\n\nThis endpoint does not follow HTTP redirects.", + "description": "Use this endpoint to fetch the favorite icon (AKA favicon) of any remote website URL.\r\n\r\nThis endpoint does not follow HTTP redirects.", "responses": { "200": { "description": "Image" @@ -3695,7 +3695,7 @@ "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. Country codes follow the [ISO 3166-1](https:\/\/en.wikipedia.org\/wiki\/ISO_3166-1) standard.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n", + "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. Country codes follow the [ISO 3166-1](https:\/\/en.wikipedia.org\/wiki\/ISO_3166-1) standard.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n", "responses": { "200": { "description": "Image" @@ -4185,7 +4185,7 @@ "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.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 400x400px.\n\nThis endpoint does not follow HTTP redirects.", + "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.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 400x400px.\r\n\r\nThis endpoint does not follow HTTP redirects.", "responses": { "200": { "description": "Image" @@ -4269,7 +4269,7 @@ "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.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n", + "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.\r\n\r\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.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n", "responses": { "200": { "description": "Image" @@ -4363,7 +4363,7 @@ "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.\n", + "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.\r\n", "responses": { "200": { "description": "Image" @@ -4644,7 +4644,7 @@ "tags": [ "databases" ], - "description": "Create a new Database.\n", + "description": "Create a new Database.\r\n", "responses": { "201": { "description": "Database", @@ -5523,7 +5523,7 @@ "tags": [ "databases" ], - "description": "Create a boolean attribute.\n", + "description": "Create a boolean attribute.\r\n", "responses": { "202": { "description": "AttributeBoolean", @@ -5965,7 +5965,7 @@ "tags": [ "databases" ], - "description": "Create an email attribute.\n", + "description": "Create an email attribute.\r\n", "responses": { "202": { "description": "AttributeEmail", @@ -6073,7 +6073,7 @@ "tags": [ "databases" ], - "description": "Update an email attribute. Changing the `default` value will not update already existing documents.\n", + "description": "Update an email attribute. Changing the `default` value will not update already existing documents.\r\n", "responses": { "200": { "description": "AttributeEmail", @@ -6186,7 +6186,7 @@ "tags": [ "databases" ], - "description": "Create an enumeration attribute. The `elements` param acts as a white-list of accepted values for this attribute. \n", + "description": "Create an enumeration attribute. The `elements` param acts as a white-list of accepted values for this attribute. \r\n", "responses": { "202": { "description": "AttributeEnum", @@ -6303,7 +6303,7 @@ "tags": [ "databases" ], - "description": "Update an enum attribute. Changing the `default` value will not update already existing documents.\n", + "description": "Update an enum attribute. Changing the `default` value will not update already existing documents.\r\n", "responses": { "200": { "description": "AttributeEnum", @@ -6425,7 +6425,7 @@ "tags": [ "databases" ], - "description": "Create a float attribute. Optionally, minimum and maximum values can be provided.\n", + "description": "Create a float attribute. Optionally, minimum and maximum values can be provided.\r\n", "responses": { "202": { "description": "AttributeFloat", @@ -6543,7 +6543,7 @@ "tags": [ "databases" ], - "description": "Update a float attribute. Changing the `default` value will not update already existing documents.\n", + "description": "Update a float attribute. Changing the `default` value will not update already existing documents.\r\n", "responses": { "200": { "description": "AttributeFloat", @@ -6668,7 +6668,7 @@ "tags": [ "databases" ], - "description": "Create an integer attribute. Optionally, minimum and maximum values can be provided.\n", + "description": "Create an integer attribute. Optionally, minimum and maximum values can be provided.\r\n", "responses": { "202": { "description": "AttributeInteger", @@ -6786,7 +6786,7 @@ "tags": [ "databases" ], - "description": "Update an integer attribute. Changing the `default` value will not update already existing documents.\n", + "description": "Update an integer attribute. Changing the `default` value will not update already existing documents.\r\n", "responses": { "200": { "description": "AttributeInteger", @@ -6911,7 +6911,7 @@ "tags": [ "databases" ], - "description": "Create IP address attribute.\n", + "description": "Create IP address attribute.\r\n", "responses": { "202": { "description": "AttributeIP", @@ -7019,7 +7019,7 @@ "tags": [ "databases" ], - "description": "Update an ip attribute. Changing the `default` value will not update already existing documents.\n", + "description": "Update an ip attribute. Changing the `default` value will not update already existing documents.\r\n", "responses": { "200": { "description": "AttributeIP", @@ -7132,7 +7132,7 @@ "tags": [ "databases" ], - "description": "Create relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\n", + "description": "Create relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\r\n", "responses": { "202": { "description": "AttributeRelationship", @@ -7265,7 +7265,7 @@ "tags": [ "databases" ], - "description": "Create a string attribute.\n", + "description": "Create a string attribute.\r\n", "responses": { "202": { "description": "AttributeString", @@ -7384,7 +7384,7 @@ "tags": [ "databases" ], - "description": "Update a string attribute. Changing the `default` value will not update already existing documents.\n", + "description": "Update a string attribute. Changing the `default` value will not update already existing documents.\r\n", "responses": { "200": { "description": "AttributeString", @@ -7502,7 +7502,7 @@ "tags": [ "databases" ], - "description": "Create a URL attribute.\n", + "description": "Create a URL attribute.\r\n", "responses": { "202": { "description": "AttributeURL", @@ -7610,7 +7610,7 @@ "tags": [ "databases" ], - "description": "Update an url attribute. Changing the `default` value will not update already existing documents.\n", + "description": "Update an url attribute. Changing the `default` value will not update already existing documents.\r\n", "responses": { "200": { "description": "AttributeURL", @@ -7909,7 +7909,7 @@ "tags": [ "databases" ], - "description": "Update relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\n", + "description": "Update relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\r\n", "responses": { "200": { "description": "AttributeRelationship", @@ -8678,7 +8678,7 @@ "tags": [ "databases" ], - "description": "Creates an index on the attributes listed. Your index should include all the attributes you will query in a single request.\nAttributes can be `key`, `fulltext`, and `unique`.", + "description": "Creates an index on the attributes listed. Your index should include all the attributes you will query in a single request.\r\nAttributes can be `key`, `fulltext`, and `unique`.", "responses": { "202": { "description": "Index", @@ -9658,7 +9658,7 @@ "tags": [ "functions" ], - "description": "List allowed function specifications for this instance.\n", + "description": "List allowed function specifications for this instance.\r\n", "responses": { "200": { "description": "Specifications List", @@ -10373,7 +10373,7 @@ "tags": [ "functions" ], - "description": "Create a new function code deployment. 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 deployment to use your new deployment 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](https:\/\/appwrite.io\/docs\/functions).\n\nUse the \"command\" param to set the entrypoint used to execute your code.", + "description": "Create a new function code deployment. 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 deployment to use your new deployment UID.\r\n\r\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](https:\/\/appwrite.io\/docs\/functions).\r\n\r\nUse the \"command\" param to set the entrypoint used to execute your code.", "responses": { "202": { "description": "Deployment", @@ -11179,7 +11179,7 @@ "tags": [ "functions" ], - "description": "Delete a function execution by its unique ID.\n", + "description": "Delete a function execution by its unique ID.\r\n", "responses": { "204": { "description": "No content" @@ -12453,7 +12453,7 @@ "tags": [ "health" ], - "description": "Returns the amount of failed jobs in a given queue.\n", + "description": "Returns the amount of failed jobs in a given queue.\r\n", "responses": { "200": { "description": "Health Queue", @@ -13208,7 +13208,7 @@ "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))", + "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.\r\n\r\n([IP Geolocation by DB-IP](https:\/\/db-ip.com))", "responses": { "200": { "description": "Locale", @@ -13864,7 +13864,7 @@ "tags": [ "messaging" ], - "description": "Update an email message by its unique ID.\n", + "description": "Update an email message by its unique ID.\r\n", "responses": { "200": { "description": "Message", @@ -14169,7 +14169,7 @@ "tags": [ "messaging" ], - "description": "Update a push notification by its unique ID.\n", + "description": "Update a push notification by its unique ID.\r\n", "responses": { "200": { "description": "Message", @@ -14439,7 +14439,7 @@ "tags": [ "messaging" ], - "description": "Update an email message by its unique ID.\n", + "description": "Update an email message by its unique ID.\r\n", "responses": { "200": { "description": "Message", @@ -14553,7 +14553,7 @@ "tags": [ "messaging" ], - "description": "Get a message by its unique ID.\n", + "description": "Get a message by its unique ID.\r\n", "responses": { "200": { "description": "Message", @@ -17029,7 +17029,7 @@ "tags": [ "messaging" ], - "description": "Get a provider by its unique ID.\n", + "description": "Get a provider by its unique ID.\r\n", "responses": { "200": { "description": "Provider", @@ -17463,7 +17463,7 @@ "tags": [ "messaging" ], - "description": "Get a topic by its unique ID.\n", + "description": "Get a topic by its unique ID.\r\n", "responses": { "200": { "description": "Topic", @@ -17525,7 +17525,7 @@ "tags": [ "messaging" ], - "description": "Update a topic by its unique ID.\n", + "description": "Update a topic by its unique ID.\r\n", "responses": { "200": { "description": "Topic", @@ -17923,7 +17923,7 @@ "tags": [ "messaging" ], - "description": "Get a subscriber by its unique ID.\n", + "description": "Get a subscriber by its unique ID.\r\n", "responses": { "200": { "description": "Subscriber", @@ -22620,7 +22620,8 @@ "description": "Does SMTP server use secure connection", "x-example": "tls", "enum": [ - "tls" + "tls", + "ssl" ], "x-enum-name": "SMTPSecure", "x-enum-keys": [] @@ -25524,7 +25525,7 @@ "tags": [ "storage" ], - "description": "Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/storage#storageCreateBucket) API or directly from your Appwrite console.\n\nLarger files should be uploaded using multiple requests with the [content-range](https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Content-Range) header to send a partial request with a maximum supported chunk of `5MB`. The `content-range` header values should always be in bytes.\n\nWhen the first request is sent, the server will return the **File** object, and the subsequent part request must include the file's **id** in `x-appwrite-id` header to allow the server to know that the partial upload is for the existing file and not for a new one.\n\nIf you're creating a new file using one of the Appwrite SDKs, all the chunking logic will be managed by the SDK internally.\n", + "description": "Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/storage#storageCreateBucket) API or directly from your Appwrite console.\r\n\r\nLarger files should be uploaded using multiple requests with the [content-range](https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Content-Range) header to send a partial request with a maximum supported chunk of `5MB`. The `content-range` header values should always be in bytes.\r\n\r\nWhen the first request is sent, the server will return the **File** object, and the subsequent part request must include the file's **id** in `x-appwrite-id` header to allow the server to know that the partial upload is for the existing file and not for a new one.\r\n\r\nIf you're creating a new file using one of the Appwrite SDKs, all the chunking logic will be managed by the SDK internally.\r\n", "responses": { "201": { "description": "File", @@ -26133,7 +26134,8 @@ "jpeg", "gif", "png", - "webp" + "webp", + "avif" ], "x-enum-name": "ImageFormat", "x-enum-keys": [], @@ -26912,7 +26914,7 @@ "tags": [ "teams" ], - "description": "Invite a new member to join your team. Provide an ID for existing users, or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team.\n\nYou only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters.\n\nUse the `url` parameter to redirect the user from the invitation email to your app. After the user is redirected, use the [Update Team Membership Status](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team. \n\nPlease note that to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console.\n", + "description": "Invite a new member to join your team. Provide an ID for existing users, or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team.\r\n\r\nYou only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters.\r\n\r\nUse the `url` parameter to redirect the user from the invitation email to your app. After the user is redirected, use the [Update Team Membership Status](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team. \r\n\r\nPlease note that to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console.\r\n", "responses": { "201": { "description": "Membership", @@ -27099,7 +27101,7 @@ "tags": [ "teams" ], - "description": "Modify the roles of a team member. Only team members with the owner role have access to this endpoint. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions).\n", + "description": "Modify the roles of a team member. Only team members with the owner role have access to this endpoint. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions).\r\n", "responses": { "200": { "description": "Membership", @@ -27264,7 +27266,7 @@ "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 received by the user.\n\nIf the request is successful, a session for the user is automatically created.\n", + "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 received by the user.\r\n\r\nIf the request is successful, a session for the user is automatically created.\r\n", "responses": { "200": { "description": "Membership", @@ -28840,7 +28842,7 @@ "tags": [ "users" ], - "description": "Update the user labels by its unique ID. \n\nLabels can be used to grant access to resources. While teams are a way for user's to share access to a resource, labels can be defined by the developer to grant access without an invitation. See the [Permissions docs](https:\/\/appwrite.io\/docs\/permissions) for more info.", + "description": "Update the user labels by its unique ID. \r\n\r\nLabels can be used to grant access to resources. While teams are a way for user's to share access to a resource, labels can be defined by the developer to grant access without an invitation. See the [Permissions docs](https:\/\/appwrite.io\/docs\/permissions) for more info.", "responses": { "200": { "description": "User", @@ -29922,7 +29924,7 @@ "tags": [ "users" ], - "description": "Creates a session for a user. Returns an immediately usable session object.\n\nIf you want to generate a token for a custom authentication flow, use the [POST \/users\/{userId}\/tokens](https:\/\/appwrite.io\/docs\/server\/users#createToken) endpoint.", + "description": "Creates a session for a user. Returns an immediately usable session object.\r\n\r\nIf you want to generate a token for a custom authentication flow, use the [POST \/users\/{userId}\/tokens](https:\/\/appwrite.io\/docs\/server\/users#createToken) endpoint.", "responses": { "201": { "description": "Session", @@ -30611,7 +30613,7 @@ "tags": [ "users" ], - "description": "Returns a token with a secret key for creating a session. Use the user ID and secret and submit a request to the [PUT \/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process.\n", + "description": "Returns a token with a secret key for creating a session. Use the user ID and secret and submit a request to the [PUT \/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process.\r\n", "responses": { "201": { "description": "Token", diff --git a/app/config/specs/open-api3-latest-server.json b/app/config/specs/open-api3-latest-server.json index 1a394f6def..47fe17866a 100644 --- a/app/config/specs/open-api3-latest-server.json +++ b/app/config/specs/open-api3-latest-server.json @@ -167,7 +167,7 @@ "tags": [ "account" ], - "description": "Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. 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.\n", + "description": "Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. For security measures, user password is required to complete this request.\r\nThis endpoint can also be used to convert an anonymous account to a normal one, by passing an email address and a new password.\r\n", "responses": { "200": { "description": "User", @@ -1590,7 +1590,7 @@ "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](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createRecovery) 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.", + "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](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createRecovery) endpoint.\r\n\r\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", @@ -1825,7 +1825,7 @@ "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.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", + "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.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", "responses": { "201": { "description": "Session", @@ -2370,7 +2370,7 @@ "tags": [ "account" ], - "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's email is valid for 15 minutes.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", + "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's email is valid for 15 minutes.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", "responses": { "201": { "description": "Token", @@ -2451,7 +2451,7 @@ "tags": [ "account" ], - "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. If you are on a mobile device you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\n", + "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. If you are on a mobile device you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\r\n", "responses": { "201": { "description": "Token", @@ -2540,7 +2540,7 @@ "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. \n\nIf authentication succeeds, `userId` and `secret` of a token will be appended to the success URL as query parameters. These can be used to create a new session using the [Create session](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", + "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. \r\n\r\nIf authentication succeeds, `userId` and `secret` of a token will be appended to the success URL as query parameters. These can be used to create a new session using the [Create session](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", "responses": { "301": { "description": "File" @@ -2676,7 +2676,7 @@ "tags": [ "account" ], - "description": "Sends the user an SMS with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's phone is valid for 15 minutes.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", + "description": "Sends the user an SMS with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's phone is valid for 15 minutes.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", "responses": { "201": { "description": "Token", @@ -2755,7 +2755,7 @@ "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](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). 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", + "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](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\r\n\r\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.\r\n", "responses": { "201": { "description": "Token", @@ -3039,7 +3039,7 @@ "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 [GET \/account\/sessions](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#getSessions) endpoint. Use width, height and quality arguments to change the output settings.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.", + "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 [GET \/account\/sessions](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#getSessions) endpoint. Use width, height and quality arguments to change the output settings.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.", "responses": { "200": { "description": "Image" @@ -3169,7 +3169,7 @@ "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.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n", + "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.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n", "responses": { "200": { "description": "Image" @@ -3303,7 +3303,7 @@ "tags": [ "avatars" ], - "description": "Use this endpoint to fetch the favorite icon (AKA favicon) of any remote website URL.\n\nThis endpoint does not follow HTTP redirects.", + "description": "Use this endpoint to fetch the favorite icon (AKA favicon) of any remote website URL.\r\n\r\nThis endpoint does not follow HTTP redirects.", "responses": { "200": { "description": "Image" @@ -3365,7 +3365,7 @@ "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. Country codes follow the [ISO 3166-1](https:\/\/en.wikipedia.org\/wiki\/ISO_3166-1) standard.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n", + "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. Country codes follow the [ISO 3166-1](https:\/\/en.wikipedia.org\/wiki\/ISO_3166-1) standard.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n", "responses": { "200": { "description": "Image" @@ -3857,7 +3857,7 @@ "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.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 400x400px.\n\nThis endpoint does not follow HTTP redirects.", + "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.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 400x400px.\r\n\r\nThis endpoint does not follow HTTP redirects.", "responses": { "200": { "description": "Image" @@ -3943,7 +3943,7 @@ "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.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n", + "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.\r\n\r\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.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n", "responses": { "200": { "description": "Image" @@ -4039,7 +4039,7 @@ "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.\n", + "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.\r\n", "responses": { "200": { "description": "Image" @@ -4211,7 +4211,7 @@ "tags": [ "databases" ], - "description": "Create a new Database.\n", + "description": "Create a new Database.\r\n", "responses": { "201": { "description": "Database", @@ -5026,7 +5026,7 @@ "tags": [ "databases" ], - "description": "Create a boolean attribute.\n", + "description": "Create a boolean attribute.\r\n", "responses": { "202": { "description": "AttributeBoolean", @@ -5472,7 +5472,7 @@ "tags": [ "databases" ], - "description": "Create an email attribute.\n", + "description": "Create an email attribute.\r\n", "responses": { "202": { "description": "AttributeEmail", @@ -5581,7 +5581,7 @@ "tags": [ "databases" ], - "description": "Update an email attribute. Changing the `default` value will not update already existing documents.\n", + "description": "Update an email attribute. Changing the `default` value will not update already existing documents.\r\n", "responses": { "200": { "description": "AttributeEmail", @@ -5695,7 +5695,7 @@ "tags": [ "databases" ], - "description": "Create an enumeration attribute. The `elements` param acts as a white-list of accepted values for this attribute. \n", + "description": "Create an enumeration attribute. The `elements` param acts as a white-list of accepted values for this attribute. \r\n", "responses": { "202": { "description": "AttributeEnum", @@ -5813,7 +5813,7 @@ "tags": [ "databases" ], - "description": "Update an enum attribute. Changing the `default` value will not update already existing documents.\n", + "description": "Update an enum attribute. Changing the `default` value will not update already existing documents.\r\n", "responses": { "200": { "description": "AttributeEnum", @@ -5936,7 +5936,7 @@ "tags": [ "databases" ], - "description": "Create a float attribute. Optionally, minimum and maximum values can be provided.\n", + "description": "Create a float attribute. Optionally, minimum and maximum values can be provided.\r\n", "responses": { "202": { "description": "AttributeFloat", @@ -6055,7 +6055,7 @@ "tags": [ "databases" ], - "description": "Update a float attribute. Changing the `default` value will not update already existing documents.\n", + "description": "Update a float attribute. Changing the `default` value will not update already existing documents.\r\n", "responses": { "200": { "description": "AttributeFloat", @@ -6181,7 +6181,7 @@ "tags": [ "databases" ], - "description": "Create an integer attribute. Optionally, minimum and maximum values can be provided.\n", + "description": "Create an integer attribute. Optionally, minimum and maximum values can be provided.\r\n", "responses": { "202": { "description": "AttributeInteger", @@ -6300,7 +6300,7 @@ "tags": [ "databases" ], - "description": "Update an integer attribute. Changing the `default` value will not update already existing documents.\n", + "description": "Update an integer attribute. Changing the `default` value will not update already existing documents.\r\n", "responses": { "200": { "description": "AttributeInteger", @@ -6426,7 +6426,7 @@ "tags": [ "databases" ], - "description": "Create IP address attribute.\n", + "description": "Create IP address attribute.\r\n", "responses": { "202": { "description": "AttributeIP", @@ -6535,7 +6535,7 @@ "tags": [ "databases" ], - "description": "Update an ip attribute. Changing the `default` value will not update already existing documents.\n", + "description": "Update an ip attribute. Changing the `default` value will not update already existing documents.\r\n", "responses": { "200": { "description": "AttributeIP", @@ -6649,7 +6649,7 @@ "tags": [ "databases" ], - "description": "Create relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\n", + "description": "Create relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\r\n", "responses": { "202": { "description": "AttributeRelationship", @@ -6783,7 +6783,7 @@ "tags": [ "databases" ], - "description": "Create a string attribute.\n", + "description": "Create a string attribute.\r\n", "responses": { "202": { "description": "AttributeString", @@ -6903,7 +6903,7 @@ "tags": [ "databases" ], - "description": "Update a string attribute. Changing the `default` value will not update already existing documents.\n", + "description": "Update a string attribute. Changing the `default` value will not update already existing documents.\r\n", "responses": { "200": { "description": "AttributeString", @@ -7022,7 +7022,7 @@ "tags": [ "databases" ], - "description": "Create a URL attribute.\n", + "description": "Create a URL attribute.\r\n", "responses": { "202": { "description": "AttributeURL", @@ -7131,7 +7131,7 @@ "tags": [ "databases" ], - "description": "Update an url attribute. Changing the `default` value will not update already existing documents.\n", + "description": "Update an url attribute. Changing the `default` value will not update already existing documents.\r\n", "responses": { "200": { "description": "AttributeURL", @@ -7433,7 +7433,7 @@ "tags": [ "databases" ], - "description": "Update relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\n", + "description": "Update relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\r\n", "responses": { "200": { "description": "AttributeRelationship", @@ -8119,7 +8119,7 @@ "tags": [ "databases" ], - "description": "Creates an index on the attributes listed. Your index should include all the attributes you will query in a single request.\nAttributes can be `key`, `fulltext`, and `unique`.", + "description": "Creates an index on the attributes listed. Your index should include all the attributes you will query in a single request.\r\nAttributes can be `key`, `fulltext`, and `unique`.", "responses": { "202": { "description": "Index", @@ -8767,7 +8767,7 @@ "tags": [ "functions" ], - "description": "List allowed function specifications for this instance.\n", + "description": "List allowed function specifications for this instance.\r\n", "responses": { "200": { "description": "Specifications List", @@ -9249,7 +9249,7 @@ "tags": [ "functions" ], - "description": "Create a new function code deployment. 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 deployment to use your new deployment 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](https:\/\/appwrite.io\/docs\/functions).\n\nUse the \"command\" param to set the entrypoint used to execute your code.", + "description": "Create a new function code deployment. 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 deployment to use your new deployment UID.\r\n\r\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](https:\/\/appwrite.io\/docs\/functions).\r\n\r\nUse the \"command\" param to set the entrypoint used to execute your code.", "responses": { "202": { "description": "Deployment", @@ -10068,7 +10068,7 @@ "tags": [ "functions" ], - "description": "Delete a function execution by its unique ID.\n", + "description": "Delete a function execution by its unique ID.\r\n", "responses": { "204": { "description": "No content" @@ -11279,7 +11279,7 @@ "tags": [ "health" ], - "description": "Returns the amount of failed jobs in a given queue.\n", + "description": "Returns the amount of failed jobs in a given queue.\r\n", "responses": { "200": { "description": "Health Queue", @@ -12046,7 +12046,7 @@ "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))", + "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.\r\n\r\n([IP Geolocation by DB-IP](https:\/\/db-ip.com))", "responses": { "200": { "description": "Locale", @@ -12720,7 +12720,7 @@ "tags": [ "messaging" ], - "description": "Update an email message by its unique ID.\n", + "description": "Update an email message by its unique ID.\r\n", "responses": { "200": { "description": "Message", @@ -13027,7 +13027,7 @@ "tags": [ "messaging" ], - "description": "Update a push notification by its unique ID.\n", + "description": "Update a push notification by its unique ID.\r\n", "responses": { "200": { "description": "Message", @@ -13299,7 +13299,7 @@ "tags": [ "messaging" ], - "description": "Update an email message by its unique ID.\n", + "description": "Update an email message by its unique ID.\r\n", "responses": { "200": { "description": "Message", @@ -13414,7 +13414,7 @@ "tags": [ "messaging" ], - "description": "Get a message by its unique ID.\n", + "description": "Get a message by its unique ID.\r\n", "responses": { "200": { "description": "Message", @@ -15915,7 +15915,7 @@ "tags": [ "messaging" ], - "description": "Get a provider by its unique ID.\n", + "description": "Get a provider by its unique ID.\r\n", "responses": { "200": { "description": "Provider", @@ -16355,7 +16355,7 @@ "tags": [ "messaging" ], - "description": "Get a topic by its unique ID.\n", + "description": "Get a topic by its unique ID.\r\n", "responses": { "200": { "description": "Topic", @@ -16418,7 +16418,7 @@ "tags": [ "messaging" ], - "description": "Update a topic by its unique ID.\n", + "description": "Update a topic by its unique ID.\r\n", "responses": { "200": { "description": "Topic", @@ -16822,7 +16822,7 @@ "tags": [ "messaging" ], - "description": "Get a subscriber by its unique ID.\n", + "description": "Get a subscriber by its unique ID.\r\n", "responses": { "200": { "description": "Subscriber", @@ -17516,7 +17516,7 @@ "tags": [ "storage" ], - "description": "Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/storage#storageCreateBucket) API or directly from your Appwrite console.\n\nLarger files should be uploaded using multiple requests with the [content-range](https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Content-Range) header to send a partial request with a maximum supported chunk of `5MB`. The `content-range` header values should always be in bytes.\n\nWhen the first request is sent, the server will return the **File** object, and the subsequent part request must include the file's **id** in `x-appwrite-id` header to allow the server to know that the partial upload is for the existing file and not for a new one.\n\nIf you're creating a new file using one of the Appwrite SDKs, all the chunking logic will be managed by the SDK internally.\n", + "description": "Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/storage#storageCreateBucket) API or directly from your Appwrite console.\r\n\r\nLarger files should be uploaded using multiple requests with the [content-range](https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Content-Range) header to send a partial request with a maximum supported chunk of `5MB`. The `content-range` header values should always be in bytes.\r\n\r\nWhen the first request is sent, the server will return the **File** object, and the subsequent part request must include the file's **id** in `x-appwrite-id` header to allow the server to know that the partial upload is for the existing file and not for a new one.\r\n\r\nIf you're creating a new file using one of the Appwrite SDKs, all the chunking logic will be managed by the SDK internally.\r\n", "responses": { "201": { "description": "File", @@ -18137,7 +18137,8 @@ "jpeg", "gif", "png", - "webp" + "webp", + "avif" ], "x-enum-name": "ImageFormat", "x-enum-keys": [], @@ -18697,7 +18698,7 @@ "tags": [ "teams" ], - "description": "Invite a new member to join your team. Provide an ID for existing users, or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team.\n\nYou only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters.\n\nUse the `url` parameter to redirect the user from the invitation email to your app. After the user is redirected, use the [Update Team Membership Status](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team. \n\nPlease note that to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console.\n", + "description": "Invite a new member to join your team. Provide an ID for existing users, or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team.\r\n\r\nYou only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters.\r\n\r\nUse the `url` parameter to redirect the user from the invitation email to your app. After the user is redirected, use the [Update Team Membership Status](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team. \r\n\r\nPlease note that to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console.\r\n", "responses": { "201": { "description": "Membership", @@ -18888,7 +18889,7 @@ "tags": [ "teams" ], - "description": "Modify the roles of a team member. Only team members with the owner role have access to this endpoint. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions).\n", + "description": "Modify the roles of a team member. Only team members with the owner role have access to this endpoint. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions).\r\n", "responses": { "200": { "description": "Membership", @@ -19057,7 +19058,7 @@ "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 received by the user.\n\nIf the request is successful, a session for the user is automatically created.\n", + "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 received by the user.\r\n\r\nIf the request is successful, a session for the user is automatically created.\r\n", "responses": { "200": { "description": "Membership", @@ -20580,7 +20581,7 @@ "tags": [ "users" ], - "description": "Update the user labels by its unique ID. \n\nLabels can be used to grant access to resources. While teams are a way for user's to share access to a resource, labels can be defined by the developer to grant access without an invitation. See the [Permissions docs](https:\/\/appwrite.io\/docs\/permissions) for more info.", + "description": "Update the user labels by its unique ID. \r\n\r\nLabels can be used to grant access to resources. While teams are a way for user's to share access to a resource, labels can be defined by the developer to grant access without an invitation. See the [Permissions docs](https:\/\/appwrite.io\/docs\/permissions) for more info.", "responses": { "200": { "description": "User", @@ -21677,7 +21678,7 @@ "tags": [ "users" ], - "description": "Creates a session for a user. Returns an immediately usable session object.\n\nIf you want to generate a token for a custom authentication flow, use the [POST \/users\/{userId}\/tokens](https:\/\/appwrite.io\/docs\/server\/users#createToken) endpoint.", + "description": "Creates a session for a user. Returns an immediately usable session object.\r\n\r\nIf you want to generate a token for a custom authentication flow, use the [POST \/users\/{userId}\/tokens](https:\/\/appwrite.io\/docs\/server\/users#createToken) endpoint.", "responses": { "201": { "description": "Session", @@ -22375,7 +22376,7 @@ "tags": [ "users" ], - "description": "Returns a token with a secret key for creating a session. Use the user ID and secret and submit a request to the [PUT \/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process.\n", + "description": "Returns a token with a secret key for creating a session. Use the user ID and secret and submit a request to the [PUT \/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process.\r\n", "responses": { "201": { "description": "Token", diff --git a/app/config/specs/swagger2-1.6.x-client.json b/app/config/specs/swagger2-1.6.x-client.json index cf001af0e8..fce6a871a3 100644 --- a/app/config/specs/swagger2-1.6.x-client.json +++ b/app/config/specs/swagger2-1.6.x-client.json @@ -222,7 +222,7 @@ "tags": [ "account" ], - "description": "Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. 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.\n", + "description": "Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. For security measures, user password is required to complete this request.\r\nThis endpoint can also be used to convert an anonymous account to a normal one, by passing an email address and a new password.\r\n", "responses": { "200": { "description": "User", @@ -1670,7 +1670,7 @@ "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](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createRecovery) 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.", + "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](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createRecovery) endpoint.\r\n\r\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", @@ -1915,7 +1915,7 @@ "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.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", + "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.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", "responses": { "201": { "description": "Session", @@ -2075,7 +2075,7 @@ "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.\n\nIf there is already an active session, the new session will be attached to the logged-in account. If there are no active sessions, the server will attempt to look for a user with the same email address as the email received from the OAuth2 provider and attach the new session to the existing user. If no matching user is found - the server will create a new user.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\n", + "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.\r\n\r\nIf there is already an active session, the new session will be attached to the logged-in account. If there are no active sessions, the server will attempt to look for a user with the same email address as the email received from the OAuth2 provider and attach the new session to the existing user. If no matching user is found - the server will create a new user.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\r\n", "responses": { "301": { "description": "No content" @@ -2836,7 +2836,7 @@ "tags": [ "account" ], - "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's email is valid for 15 minutes.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", + "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's email is valid for 15 minutes.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", "responses": { "201": { "description": "Token", @@ -2922,7 +2922,7 @@ "tags": [ "account" ], - "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. If you are on a mobile device you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\n", + "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. If you are on a mobile device you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\r\n", "responses": { "201": { "description": "Token", @@ -3017,7 +3017,7 @@ "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. \n\nIf authentication succeeds, `userId` and `secret` of a token will be appended to the success URL as query parameters. These can be used to create a new session using the [Create session](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", + "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. \r\n\r\nIf authentication succeeds, `userId` and `secret` of a token will be appended to the success URL as query parameters. These can be used to create a new session using the [Create session](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", "responses": { "301": { "description": "No content" @@ -3152,7 +3152,7 @@ "tags": [ "account" ], - "description": "Sends the user an SMS with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's phone is valid for 15 minutes.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", + "description": "Sends the user an SMS with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's phone is valid for 15 minutes.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", "responses": { "201": { "description": "Token", @@ -3235,7 +3235,7 @@ "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](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). 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", + "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](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\r\n\r\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.\r\n", "responses": { "201": { "description": "Token", @@ -3528,7 +3528,7 @@ "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 [GET \/account\/sessions](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#getSessions) endpoint. Use width, height and quality arguments to change the output settings.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.", + "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 [GET \/account\/sessions](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#getSessions) endpoint. Use width, height and quality arguments to change the output settings.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.", "responses": { "200": { "description": "Image", @@ -3657,7 +3657,7 @@ "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.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n", + "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.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n", "responses": { "200": { "description": "Image", @@ -3790,7 +3790,7 @@ "tags": [ "avatars" ], - "description": "Use this endpoint to fetch the favorite icon (AKA favicon) of any remote website URL.\n\nThis endpoint does not follow HTTP redirects.", + "description": "Use this endpoint to fetch the favorite icon (AKA favicon) of any remote website URL.\r\n\r\nThis endpoint does not follow HTTP redirects.", "responses": { "200": { "description": "Image", @@ -3857,7 +3857,7 @@ "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. Country codes follow the [ISO 3166-1](https:\/\/en.wikipedia.org\/wiki\/ISO_3166-1) standard.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n", + "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. Country codes follow the [ISO 3166-1](https:\/\/en.wikipedia.org\/wiki\/ISO_3166-1) standard.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n", "responses": { "200": { "description": "Image", @@ -4348,7 +4348,7 @@ "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.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 400x400px.\n\nThis endpoint does not follow HTTP redirects.", + "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.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 400x400px.\r\n\r\nThis endpoint does not follow HTTP redirects.", "responses": { "200": { "description": "Image", @@ -4435,7 +4435,7 @@ "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.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n", + "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.\r\n\r\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.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n", "responses": { "200": { "description": "Image", @@ -4530,7 +4530,7 @@ "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.\n", + "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.\r\n", "responses": { "200": { "description": "Image", @@ -5528,7 +5528,7 @@ "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))", + "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.\r\n\r\n([IP Geolocation by DB-IP](https:\/\/db-ip.com))", "responses": { "200": { "description": "Locale", @@ -6225,7 +6225,7 @@ "tags": [ "storage" ], - "description": "Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/storage#storageCreateBucket) API or directly from your Appwrite console.\n\nLarger files should be uploaded using multiple requests with the [content-range](https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Content-Range) header to send a partial request with a maximum supported chunk of `5MB`. The `content-range` header values should always be in bytes.\n\nWhen the first request is sent, the server will return the **File** object, and the subsequent part request must include the file's **id** in `x-appwrite-id` header to allow the server to know that the partial upload is for the existing file and not for a new one.\n\nIf you're creating a new file using one of the Appwrite SDKs, all the chunking logic will be managed by the SDK internally.\n", + "description": "Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/storage#storageCreateBucket) API or directly from your Appwrite console.\r\n\r\nLarger files should be uploaded using multiple requests with the [content-range](https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Content-Range) header to send a partial request with a maximum supported chunk of `5MB`. The `content-range` header values should always be in bytes.\r\n\r\nWhen the first request is sent, the server will return the **File** object, and the subsequent part request must include the file's **id** in `x-appwrite-id` header to allow the server to know that the partial upload is for the existing file and not for a new one.\r\n\r\nIf you're creating a new file using one of the Appwrite SDKs, all the chunking logic will be managed by the SDK internally.\r\n", "responses": { "201": { "description": "File", @@ -6807,7 +6807,8 @@ "jpeg", "gif", "png", - "webp" + "webp", + "avif" ], "x-enum-name": "ImageFormat", "x-enum-keys": [], @@ -7367,7 +7368,7 @@ "tags": [ "teams" ], - "description": "Invite a new member to join your team. Provide an ID for existing users, or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team.\n\nYou only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters.\n\nUse the `url` parameter to redirect the user from the invitation email to your app. After the user is redirected, use the [Update Team Membership Status](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team. \n\nPlease note that to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console.\n", + "description": "Invite a new member to join your team. Provide an ID for existing users, or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team.\r\n\r\nYou only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters.\r\n\r\nUse the `url` parameter to redirect the user from the invitation email to your app. After the user is redirected, use the [Update Team Membership Status](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team. \r\n\r\nPlease note that to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console.\r\n", "responses": { "201": { "description": "Membership", @@ -7556,7 +7557,7 @@ "tags": [ "teams" ], - "description": "Modify the roles of a team member. Only team members with the owner role have access to this endpoint. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions).\n", + "description": "Modify the roles of a team member. Only team members with the owner role have access to this endpoint. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions).\r\n", "responses": { "200": { "description": "Membership", @@ -7718,7 +7719,7 @@ "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 received by the user.\n\nIf the request is successful, a session for the user is automatically created.\n", + "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 received by the user.\r\n\r\nIf the request is successful, a session for the user is automatically created.\r\n", "responses": { "200": { "description": "Membership", diff --git a/app/config/specs/swagger2-1.6.x-console.json b/app/config/specs/swagger2-1.6.x-console.json index d9037abc55..bd2aeaa518 100644 --- a/app/config/specs/swagger2-1.6.x-console.json +++ b/app/config/specs/swagger2-1.6.x-console.json @@ -278,7 +278,7 @@ "tags": [ "account" ], - "description": "Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. 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.\n", + "description": "Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. For security measures, user password is required to complete this request.\r\nThis endpoint can also be used to convert an anonymous account to a normal one, by passing an email address and a new password.\r\n", "responses": { "200": { "description": "User", @@ -1707,7 +1707,7 @@ "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](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createRecovery) 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.", + "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](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createRecovery) endpoint.\r\n\r\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", @@ -1949,7 +1949,7 @@ "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.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", + "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.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", "responses": { "201": { "description": "Session", @@ -2109,7 +2109,7 @@ "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.\n\nIf there is already an active session, the new session will be attached to the logged-in account. If there are no active sessions, the server will attempt to look for a user with the same email address as the email received from the OAuth2 provider and attach the new session to the existing user. If no matching user is found - the server will create a new user.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\n", + "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.\r\n\r\nIf there is already an active session, the new session will be attached to the logged-in account. If there are no active sessions, the server will attempt to look for a user with the same email address as the email received from the OAuth2 provider and attach the new session to the existing user. If no matching user is found - the server will create a new user.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\r\n", "responses": { "301": { "description": "No content" @@ -2863,7 +2863,7 @@ "tags": [ "account" ], - "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's email is valid for 15 minutes.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", + "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's email is valid for 15 minutes.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", "responses": { "201": { "description": "Token", @@ -2949,7 +2949,7 @@ "tags": [ "account" ], - "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. If you are on a mobile device you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\n", + "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. If you are on a mobile device you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\r\n", "responses": { "201": { "description": "Token", @@ -3044,7 +3044,7 @@ "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. \n\nIf authentication succeeds, `userId` and `secret` of a token will be appended to the success URL as query parameters. These can be used to create a new session using the [Create session](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", + "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. \r\n\r\nIf authentication succeeds, `userId` and `secret` of a token will be appended to the success URL as query parameters. These can be used to create a new session using the [Create session](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", "responses": { "301": { "description": "No content" @@ -3179,7 +3179,7 @@ "tags": [ "account" ], - "description": "Sends the user an SMS with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's phone is valid for 15 minutes.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", + "description": "Sends the user an SMS with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's phone is valid for 15 minutes.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", "responses": { "201": { "description": "Token", @@ -3262,7 +3262,7 @@ "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](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). 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", + "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](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\r\n\r\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.\r\n", "responses": { "201": { "description": "Token", @@ -3551,7 +3551,7 @@ "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 [GET \/account\/sessions](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#getSessions) endpoint. Use width, height and quality arguments to change the output settings.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.", + "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 [GET \/account\/sessions](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#getSessions) endpoint. Use width, height and quality arguments to change the output settings.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.", "responses": { "200": { "description": "Image", @@ -3680,7 +3680,7 @@ "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.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n", + "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.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n", "responses": { "200": { "description": "Image", @@ -3813,7 +3813,7 @@ "tags": [ "avatars" ], - "description": "Use this endpoint to fetch the favorite icon (AKA favicon) of any remote website URL.\n\nThis endpoint does not follow HTTP redirects.", + "description": "Use this endpoint to fetch the favorite icon (AKA favicon) of any remote website URL.\r\n\r\nThis endpoint does not follow HTTP redirects.", "responses": { "200": { "description": "Image", @@ -3880,7 +3880,7 @@ "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. Country codes follow the [ISO 3166-1](https:\/\/en.wikipedia.org\/wiki\/ISO_3166-1) standard.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n", + "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. Country codes follow the [ISO 3166-1](https:\/\/en.wikipedia.org\/wiki\/ISO_3166-1) standard.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n", "responses": { "200": { "description": "Image", @@ -4371,7 +4371,7 @@ "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.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 400x400px.\n\nThis endpoint does not follow HTTP redirects.", + "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.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 400x400px.\r\n\r\nThis endpoint does not follow HTTP redirects.", "responses": { "200": { "description": "Image", @@ -4458,7 +4458,7 @@ "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.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n", + "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.\r\n\r\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.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n", "responses": { "200": { "description": "Image", @@ -4553,7 +4553,7 @@ "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.\n", + "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.\r\n", "responses": { "200": { "description": "Image", @@ -4846,7 +4846,7 @@ "tags": [ "databases" ], - "description": "Create a new Database.\n", + "description": "Create a new Database.\r\n", "responses": { "201": { "description": "Database", @@ -5727,7 +5727,7 @@ "tags": [ "databases" ], - "description": "Create a boolean attribute.\n", + "description": "Create a boolean attribute.\r\n", "responses": { "202": { "description": "AttributeBoolean", @@ -6159,7 +6159,7 @@ "tags": [ "databases" ], - "description": "Create an email attribute.\n", + "description": "Create an email attribute.\r\n", "responses": { "202": { "description": "AttributeEmail", @@ -6265,7 +6265,7 @@ "tags": [ "databases" ], - "description": "Update an email attribute. Changing the `default` value will not update already existing documents.\n", + "description": "Update an email attribute. Changing the `default` value will not update already existing documents.\r\n", "responses": { "200": { "description": "AttributeEmail", @@ -6375,7 +6375,7 @@ "tags": [ "databases" ], - "description": "Create an enumeration attribute. The `elements` param acts as a white-list of accepted values for this attribute. \n", + "description": "Create an enumeration attribute. The `elements` param acts as a white-list of accepted values for this attribute. \r\n", "responses": { "202": { "description": "AttributeEnum", @@ -6491,7 +6491,7 @@ "tags": [ "databases" ], - "description": "Update an enum attribute. Changing the `default` value will not update already existing documents.\n", + "description": "Update an enum attribute. Changing the `default` value will not update already existing documents.\r\n", "responses": { "200": { "description": "AttributeEnum", @@ -6611,7 +6611,7 @@ "tags": [ "databases" ], - "description": "Create a float attribute. Optionally, minimum and maximum values can be provided.\n", + "description": "Create a float attribute. Optionally, minimum and maximum values can be provided.\r\n", "responses": { "202": { "description": "AttributeFloat", @@ -6729,7 +6729,7 @@ "tags": [ "databases" ], - "description": "Update a float attribute. Changing the `default` value will not update already existing documents.\n", + "description": "Update a float attribute. Changing the `default` value will not update already existing documents.\r\n", "responses": { "200": { "description": "AttributeFloat", @@ -6853,7 +6853,7 @@ "tags": [ "databases" ], - "description": "Create an integer attribute. Optionally, minimum and maximum values can be provided.\n", + "description": "Create an integer attribute. Optionally, minimum and maximum values can be provided.\r\n", "responses": { "202": { "description": "AttributeInteger", @@ -6971,7 +6971,7 @@ "tags": [ "databases" ], - "description": "Update an integer attribute. Changing the `default` value will not update already existing documents.\n", + "description": "Update an integer attribute. Changing the `default` value will not update already existing documents.\r\n", "responses": { "200": { "description": "AttributeInteger", @@ -7095,7 +7095,7 @@ "tags": [ "databases" ], - "description": "Create IP address attribute.\n", + "description": "Create IP address attribute.\r\n", "responses": { "202": { "description": "AttributeIP", @@ -7201,7 +7201,7 @@ "tags": [ "databases" ], - "description": "Update an ip attribute. Changing the `default` value will not update already existing documents.\n", + "description": "Update an ip attribute. Changing the `default` value will not update already existing documents.\r\n", "responses": { "200": { "description": "AttributeIP", @@ -7311,7 +7311,7 @@ "tags": [ "databases" ], - "description": "Create relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\n", + "description": "Create relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\r\n", "responses": { "202": { "description": "AttributeRelationship", @@ -7446,7 +7446,7 @@ "tags": [ "databases" ], - "description": "Create a string attribute.\n", + "description": "Create a string attribute.\r\n", "responses": { "202": { "description": "AttributeString", @@ -7565,7 +7565,7 @@ "tags": [ "databases" ], - "description": "Update a string attribute. Changing the `default` value will not update already existing documents.\n", + "description": "Update a string attribute. Changing the `default` value will not update already existing documents.\r\n", "responses": { "200": { "description": "AttributeString", @@ -7681,7 +7681,7 @@ "tags": [ "databases" ], - "description": "Create a URL attribute.\n", + "description": "Create a URL attribute.\r\n", "responses": { "202": { "description": "AttributeURL", @@ -7787,7 +7787,7 @@ "tags": [ "databases" ], - "description": "Update an url attribute. Changing the `default` value will not update already existing documents.\n", + "description": "Update an url attribute. Changing the `default` value will not update already existing documents.\r\n", "responses": { "200": { "description": "AttributeURL", @@ -8075,7 +8075,7 @@ "tags": [ "databases" ], - "description": "Update relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\n", + "description": "Update relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\r\n", "responses": { "200": { "description": "AttributeRelationship", @@ -8817,7 +8817,7 @@ "tags": [ "databases" ], - "description": "Creates an index on the attributes listed. Your index should include all the attributes you will query in a single request.\nAttributes can be `key`, `fulltext`, and `unique`.", + "description": "Creates an index on the attributes listed. Your index should include all the attributes you will query in a single request.\r\nAttributes can be `key`, `fulltext`, and `unique`.", "responses": { "202": { "description": "Index", @@ -9806,7 +9806,7 @@ "tags": [ "functions" ], - "description": "List allowed function specifications for this instance.\n", + "description": "List allowed function specifications for this instance.\r\n", "responses": { "200": { "description": "Specifications List", @@ -10533,7 +10533,7 @@ "tags": [ "functions" ], - "description": "Create a new function code deployment. 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 deployment to use your new deployment 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](https:\/\/appwrite.io\/docs\/functions).\n\nUse the \"command\" param to set the entrypoint used to execute your code.", + "description": "Create a new function code deployment. 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 deployment to use your new deployment UID.\r\n\r\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](https:\/\/appwrite.io\/docs\/functions).\r\n\r\nUse the \"command\" param to set the entrypoint used to execute your code.", "responses": { "202": { "description": "Deployment", @@ -11336,7 +11336,7 @@ "tags": [ "functions" ], - "description": "Delete a function execution by its unique ID.\n", + "description": "Delete a function execution by its unique ID.\r\n", "responses": { "204": { "description": "No content" @@ -12660,7 +12660,7 @@ "tags": [ "health" ], - "description": "Returns the amount of failed jobs in a given queue.\n", + "description": "Returns the amount of failed jobs in a given queue.\r\n", "responses": { "200": { "description": "Health Queue", @@ -13419,7 +13419,7 @@ "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))", + "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.\r\n\r\n([IP Geolocation by DB-IP](https:\/\/db-ip.com))", "responses": { "200": { "description": "Locale", @@ -14104,7 +14104,7 @@ "tags": [ "messaging" ], - "description": "Update an email message by its unique ID.\n", + "description": "Update an email message by its unique ID.\r\n", "responses": { "200": { "description": "Message", @@ -14436,7 +14436,7 @@ "tags": [ "messaging" ], - "description": "Update a push notification by its unique ID.\n", + "description": "Update a push notification by its unique ID.\r\n", "responses": { "200": { "description": "Message", @@ -14728,7 +14728,7 @@ "tags": [ "messaging" ], - "description": "Update an email message by its unique ID.\n", + "description": "Update an email message by its unique ID.\r\n", "responses": { "200": { "description": "Message", @@ -14846,7 +14846,7 @@ "tags": [ "messaging" ], - "description": "Get a message by its unique ID.\n", + "description": "Get a message by its unique ID.\r\n", "responses": { "200": { "description": "Message", @@ -17461,7 +17461,7 @@ "tags": [ "messaging" ], - "description": "Get a provider by its unique ID.\n", + "description": "Get a provider by its unique ID.\r\n", "responses": { "200": { "description": "Provider", @@ -17903,7 +17903,7 @@ "tags": [ "messaging" ], - "description": "Get a topic by its unique ID.\n", + "description": "Get a topic by its unique ID.\r\n", "responses": { "200": { "description": "Topic", @@ -17965,7 +17965,7 @@ "tags": [ "messaging" ], - "description": "Update a topic by its unique ID.\n", + "description": "Update a topic by its unique ID.\r\n", "responses": { "200": { "description": "Topic", @@ -18363,7 +18363,7 @@ "tags": [ "messaging" ], - "description": "Get a subscriber by its unique ID.\n", + "description": "Get a subscriber by its unique ID.\r\n", "responses": { "200": { "description": "Subscriber", @@ -23101,7 +23101,8 @@ "default": "", "x-example": "tls", "enum": [ - "tls" + "tls", + "ssl" ], "x-enum-name": "SMTPSecure", "x-enum-keys": [] @@ -26014,7 +26015,7 @@ "tags": [ "storage" ], - "description": "Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/storage#storageCreateBucket) API or directly from your Appwrite console.\n\nLarger files should be uploaded using multiple requests with the [content-range](https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Content-Range) header to send a partial request with a maximum supported chunk of `5MB`. The `content-range` header values should always be in bytes.\n\nWhen the first request is sent, the server will return the **File** object, and the subsequent part request must include the file's **id** in `x-appwrite-id` header to allow the server to know that the partial upload is for the existing file and not for a new one.\n\nIf you're creating a new file using one of the Appwrite SDKs, all the chunking logic will be managed by the SDK internally.\n", + "description": "Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/storage#storageCreateBucket) API or directly from your Appwrite console.\r\n\r\nLarger files should be uploaded using multiple requests with the [content-range](https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Content-Range) header to send a partial request with a maximum supported chunk of `5MB`. The `content-range` header values should always be in bytes.\r\n\r\nWhen the first request is sent, the server will return the **File** object, and the subsequent part request must include the file's **id** in `x-appwrite-id` header to allow the server to know that the partial upload is for the existing file and not for a new one.\r\n\r\nIf you're creating a new file using one of the Appwrite SDKs, all the chunking logic will be managed by the SDK internally.\r\n", "responses": { "201": { "description": "File", @@ -26596,7 +26597,8 @@ "jpeg", "gif", "png", - "webp" + "webp", + "avif" ], "x-enum-name": "ImageFormat", "x-enum-keys": [], @@ -27386,7 +27388,7 @@ "tags": [ "teams" ], - "description": "Invite a new member to join your team. Provide an ID for existing users, or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team.\n\nYou only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters.\n\nUse the `url` parameter to redirect the user from the invitation email to your app. After the user is redirected, use the [Update Team Membership Status](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team. \n\nPlease note that to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console.\n", + "description": "Invite a new member to join your team. Provide an ID for existing users, or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team.\r\n\r\nYou only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters.\r\n\r\nUse the `url` parameter to redirect the user from the invitation email to your app. After the user is redirected, use the [Update Team Membership Status](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team. \r\n\r\nPlease note that to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console.\r\n", "responses": { "201": { "description": "Membership", @@ -27575,7 +27577,7 @@ "tags": [ "teams" ], - "description": "Modify the roles of a team member. Only team members with the owner role have access to this endpoint. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions).\n", + "description": "Modify the roles of a team member. Only team members with the owner role have access to this endpoint. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions).\r\n", "responses": { "200": { "description": "Membership", @@ -27737,7 +27739,7 @@ "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 received by the user.\n\nIf the request is successful, a session for the user is automatically created.\n", + "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 received by the user.\r\n\r\nIf the request is successful, a session for the user is automatically created.\r\n", "responses": { "200": { "description": "Membership", @@ -29369,7 +29371,7 @@ "tags": [ "users" ], - "description": "Update the user labels by its unique ID. \n\nLabels can be used to grant access to resources. While teams are a way for user's to share access to a resource, labels can be defined by the developer to grant access without an invitation. See the [Permissions docs](https:\/\/appwrite.io\/docs\/permissions) for more info.", + "description": "Update the user labels by its unique ID. \r\n\r\nLabels can be used to grant access to resources. While teams are a way for user's to share access to a resource, labels can be defined by the developer to grant access without an invitation. See the [Permissions docs](https:\/\/appwrite.io\/docs\/permissions) for more info.", "responses": { "200": { "description": "User", @@ -30442,7 +30444,7 @@ "tags": [ "users" ], - "description": "Creates a session for a user. Returns an immediately usable session object.\n\nIf you want to generate a token for a custom authentication flow, use the [POST \/users\/{userId}\/tokens](https:\/\/appwrite.io\/docs\/server\/users#createToken) endpoint.", + "description": "Creates a session for a user. Returns an immediately usable session object.\r\n\r\nIf you want to generate a token for a custom authentication flow, use the [POST \/users\/{userId}\/tokens](https:\/\/appwrite.io\/docs\/server\/users#createToken) endpoint.", "responses": { "201": { "description": "Session", @@ -31133,7 +31135,7 @@ "tags": [ "users" ], - "description": "Returns a token with a secret key for creating a session. Use the user ID and secret and submit a request to the [PUT \/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process.\n", + "description": "Returns a token with a secret key for creating a session. Use the user ID and secret and submit a request to the [PUT \/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process.\r\n", "responses": { "201": { "description": "Token", diff --git a/app/config/specs/swagger2-1.6.x-server.json b/app/config/specs/swagger2-1.6.x-server.json index 42cf3ff2c7..77ac485772 100644 --- a/app/config/specs/swagger2-1.6.x-server.json +++ b/app/config/specs/swagger2-1.6.x-server.json @@ -238,7 +238,7 @@ "tags": [ "account" ], - "description": "Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. 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.\n", + "description": "Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. For security measures, user password is required to complete this request.\r\nThis endpoint can also be used to convert an anonymous account to a normal one, by passing an email address and a new password.\r\n", "responses": { "200": { "description": "User", @@ -1705,7 +1705,7 @@ "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](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createRecovery) 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.", + "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](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createRecovery) endpoint.\r\n\r\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", @@ -1953,7 +1953,7 @@ "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.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", + "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.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", "responses": { "201": { "description": "Session", @@ -2518,7 +2518,7 @@ "tags": [ "account" ], - "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's email is valid for 15 minutes.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", + "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's email is valid for 15 minutes.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", "responses": { "201": { "description": "Token", @@ -2604,7 +2604,7 @@ "tags": [ "account" ], - "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. If you are on a mobile device you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\n", + "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. If you are on a mobile device you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\r\n", "responses": { "201": { "description": "Token", @@ -2699,7 +2699,7 @@ "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. \n\nIf authentication succeeds, `userId` and `secret` of a token will be appended to the success URL as query parameters. These can be used to create a new session using the [Create session](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", + "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. \r\n\r\nIf authentication succeeds, `userId` and `secret` of a token will be appended to the success URL as query parameters. These can be used to create a new session using the [Create session](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", "responses": { "301": { "description": "No content" @@ -2834,7 +2834,7 @@ "tags": [ "account" ], - "description": "Sends the user an SMS with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's phone is valid for 15 minutes.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", + "description": "Sends the user an SMS with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's phone is valid for 15 minutes.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", "responses": { "201": { "description": "Token", @@ -2917,7 +2917,7 @@ "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](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). 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", + "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](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\r\n\r\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.\r\n", "responses": { "201": { "description": "Token", @@ -3214,7 +3214,7 @@ "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 [GET \/account\/sessions](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#getSessions) endpoint. Use width, height and quality arguments to change the output settings.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.", + "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 [GET \/account\/sessions](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#getSessions) endpoint. Use width, height and quality arguments to change the output settings.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.", "responses": { "200": { "description": "Image", @@ -3345,7 +3345,7 @@ "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.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n", + "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.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n", "responses": { "200": { "description": "Image", @@ -3480,7 +3480,7 @@ "tags": [ "avatars" ], - "description": "Use this endpoint to fetch the favorite icon (AKA favicon) of any remote website URL.\n\nThis endpoint does not follow HTTP redirects.", + "description": "Use this endpoint to fetch the favorite icon (AKA favicon) of any remote website URL.\r\n\r\nThis endpoint does not follow HTTP redirects.", "responses": { "200": { "description": "Image", @@ -3549,7 +3549,7 @@ "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. Country codes follow the [ISO 3166-1](https:\/\/en.wikipedia.org\/wiki\/ISO_3166-1) standard.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n", + "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. Country codes follow the [ISO 3166-1](https:\/\/en.wikipedia.org\/wiki\/ISO_3166-1) standard.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n", "responses": { "200": { "description": "Image", @@ -4042,7 +4042,7 @@ "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.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 400x400px.\n\nThis endpoint does not follow HTTP redirects.", + "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.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 400x400px.\r\n\r\nThis endpoint does not follow HTTP redirects.", "responses": { "200": { "description": "Image", @@ -4131,7 +4131,7 @@ "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.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n", + "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.\r\n\r\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.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n", "responses": { "200": { "description": "Image", @@ -4228,7 +4228,7 @@ "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.\n", + "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.\r\n", "responses": { "200": { "description": "Image", @@ -4400,7 +4400,7 @@ "tags": [ "databases" ], - "description": "Create a new Database.\n", + "description": "Create a new Database.\r\n", "responses": { "201": { "description": "Database", @@ -5217,7 +5217,7 @@ "tags": [ "databases" ], - "description": "Create a boolean attribute.\n", + "description": "Create a boolean attribute.\r\n", "responses": { "202": { "description": "AttributeBoolean", @@ -5653,7 +5653,7 @@ "tags": [ "databases" ], - "description": "Create an email attribute.\n", + "description": "Create an email attribute.\r\n", "responses": { "202": { "description": "AttributeEmail", @@ -5760,7 +5760,7 @@ "tags": [ "databases" ], - "description": "Update an email attribute. Changing the `default` value will not update already existing documents.\n", + "description": "Update an email attribute. Changing the `default` value will not update already existing documents.\r\n", "responses": { "200": { "description": "AttributeEmail", @@ -5871,7 +5871,7 @@ "tags": [ "databases" ], - "description": "Create an enumeration attribute. The `elements` param acts as a white-list of accepted values for this attribute. \n", + "description": "Create an enumeration attribute. The `elements` param acts as a white-list of accepted values for this attribute. \r\n", "responses": { "202": { "description": "AttributeEnum", @@ -5988,7 +5988,7 @@ "tags": [ "databases" ], - "description": "Update an enum attribute. Changing the `default` value will not update already existing documents.\n", + "description": "Update an enum attribute. Changing the `default` value will not update already existing documents.\r\n", "responses": { "200": { "description": "AttributeEnum", @@ -6109,7 +6109,7 @@ "tags": [ "databases" ], - "description": "Create a float attribute. Optionally, minimum and maximum values can be provided.\n", + "description": "Create a float attribute. Optionally, minimum and maximum values can be provided.\r\n", "responses": { "202": { "description": "AttributeFloat", @@ -6228,7 +6228,7 @@ "tags": [ "databases" ], - "description": "Update a float attribute. Changing the `default` value will not update already existing documents.\n", + "description": "Update a float attribute. Changing the `default` value will not update already existing documents.\r\n", "responses": { "200": { "description": "AttributeFloat", @@ -6353,7 +6353,7 @@ "tags": [ "databases" ], - "description": "Create an integer attribute. Optionally, minimum and maximum values can be provided.\n", + "description": "Create an integer attribute. Optionally, minimum and maximum values can be provided.\r\n", "responses": { "202": { "description": "AttributeInteger", @@ -6472,7 +6472,7 @@ "tags": [ "databases" ], - "description": "Update an integer attribute. Changing the `default` value will not update already existing documents.\n", + "description": "Update an integer attribute. Changing the `default` value will not update already existing documents.\r\n", "responses": { "200": { "description": "AttributeInteger", @@ -6597,7 +6597,7 @@ "tags": [ "databases" ], - "description": "Create IP address attribute.\n", + "description": "Create IP address attribute.\r\n", "responses": { "202": { "description": "AttributeIP", @@ -6704,7 +6704,7 @@ "tags": [ "databases" ], - "description": "Update an ip attribute. Changing the `default` value will not update already existing documents.\n", + "description": "Update an ip attribute. Changing the `default` value will not update already existing documents.\r\n", "responses": { "200": { "description": "AttributeIP", @@ -6815,7 +6815,7 @@ "tags": [ "databases" ], - "description": "Create relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\n", + "description": "Create relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\r\n", "responses": { "202": { "description": "AttributeRelationship", @@ -6951,7 +6951,7 @@ "tags": [ "databases" ], - "description": "Create a string attribute.\n", + "description": "Create a string attribute.\r\n", "responses": { "202": { "description": "AttributeString", @@ -7071,7 +7071,7 @@ "tags": [ "databases" ], - "description": "Update a string attribute. Changing the `default` value will not update already existing documents.\n", + "description": "Update a string attribute. Changing the `default` value will not update already existing documents.\r\n", "responses": { "200": { "description": "AttributeString", @@ -7188,7 +7188,7 @@ "tags": [ "databases" ], - "description": "Create a URL attribute.\n", + "description": "Create a URL attribute.\r\n", "responses": { "202": { "description": "AttributeURL", @@ -7295,7 +7295,7 @@ "tags": [ "databases" ], - "description": "Update an url attribute. Changing the `default` value will not update already existing documents.\n", + "description": "Update an url attribute. Changing the `default` value will not update already existing documents.\r\n", "responses": { "200": { "description": "AttributeURL", @@ -7586,7 +7586,7 @@ "tags": [ "databases" ], - "description": "Update relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\n", + "description": "Update relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\r\n", "responses": { "200": { "description": "AttributeRelationship", @@ -8250,7 +8250,7 @@ "tags": [ "databases" ], - "description": "Creates an index on the attributes listed. Your index should include all the attributes you will query in a single request.\nAttributes can be `key`, `fulltext`, and `unique`.", + "description": "Creates an index on the attributes listed. Your index should include all the attributes you will query in a single request.\r\nAttributes can be `key`, `fulltext`, and `unique`.", "responses": { "202": { "description": "Index", @@ -8917,7 +8917,7 @@ "tags": [ "functions" ], - "description": "List allowed function specifications for this instance.\n", + "description": "List allowed function specifications for this instance.\r\n", "responses": { "200": { "description": "Specifications List", @@ -9415,7 +9415,7 @@ "tags": [ "functions" ], - "description": "Create a new function code deployment. 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 deployment to use your new deployment 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](https:\/\/appwrite.io\/docs\/functions).\n\nUse the \"command\" param to set the entrypoint used to execute your code.", + "description": "Create a new function code deployment. 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 deployment to use your new deployment UID.\r\n\r\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](https:\/\/appwrite.io\/docs\/functions).\r\n\r\nUse the \"command\" param to set the entrypoint used to execute your code.", "responses": { "202": { "description": "Deployment", @@ -10231,7 +10231,7 @@ "tags": [ "functions" ], - "description": "Delete a function execution by its unique ID.\n", + "description": "Delete a function execution by its unique ID.\r\n", "responses": { "204": { "description": "No content" @@ -11494,7 +11494,7 @@ "tags": [ "health" ], - "description": "Returns the amount of failed jobs in a given queue.\n", + "description": "Returns the amount of failed jobs in a given queue.\r\n", "responses": { "200": { "description": "Health Queue", @@ -12265,7 +12265,7 @@ "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))", + "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.\r\n\r\n([IP Geolocation by DB-IP](https:\/\/db-ip.com))", "responses": { "200": { "description": "Locale", @@ -12968,7 +12968,7 @@ "tags": [ "messaging" ], - "description": "Update an email message by its unique ID.\n", + "description": "Update an email message by its unique ID.\r\n", "responses": { "200": { "description": "Message", @@ -13302,7 +13302,7 @@ "tags": [ "messaging" ], - "description": "Update a push notification by its unique ID.\n", + "description": "Update a push notification by its unique ID.\r\n", "responses": { "200": { "description": "Message", @@ -13596,7 +13596,7 @@ "tags": [ "messaging" ], - "description": "Update an email message by its unique ID.\n", + "description": "Update an email message by its unique ID.\r\n", "responses": { "200": { "description": "Message", @@ -13715,7 +13715,7 @@ "tags": [ "messaging" ], - "description": "Get a message by its unique ID.\n", + "description": "Get a message by its unique ID.\r\n", "responses": { "200": { "description": "Message", @@ -16355,7 +16355,7 @@ "tags": [ "messaging" ], - "description": "Get a provider by its unique ID.\n", + "description": "Get a provider by its unique ID.\r\n", "responses": { "200": { "description": "Provider", @@ -16803,7 +16803,7 @@ "tags": [ "messaging" ], - "description": "Get a topic by its unique ID.\n", + "description": "Get a topic by its unique ID.\r\n", "responses": { "200": { "description": "Topic", @@ -16866,7 +16866,7 @@ "tags": [ "messaging" ], - "description": "Update a topic by its unique ID.\n", + "description": "Update a topic by its unique ID.\r\n", "responses": { "200": { "description": "Topic", @@ -17270,7 +17270,7 @@ "tags": [ "messaging" ], - "description": "Get a subscriber by its unique ID.\n", + "description": "Get a subscriber by its unique ID.\r\n", "responses": { "200": { "description": "Subscriber", @@ -17981,7 +17981,7 @@ "tags": [ "storage" ], - "description": "Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/storage#storageCreateBucket) API or directly from your Appwrite console.\n\nLarger files should be uploaded using multiple requests with the [content-range](https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Content-Range) header to send a partial request with a maximum supported chunk of `5MB`. The `content-range` header values should always be in bytes.\n\nWhen the first request is sent, the server will return the **File** object, and the subsequent part request must include the file's **id** in `x-appwrite-id` header to allow the server to know that the partial upload is for the existing file and not for a new one.\n\nIf you're creating a new file using one of the Appwrite SDKs, all the chunking logic will be managed by the SDK internally.\n", + "description": "Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/storage#storageCreateBucket) API or directly from your Appwrite console.\r\n\r\nLarger files should be uploaded using multiple requests with the [content-range](https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Content-Range) header to send a partial request with a maximum supported chunk of `5MB`. The `content-range` header values should always be in bytes.\r\n\r\nWhen the first request is sent, the server will return the **File** object, and the subsequent part request must include the file's **id** in `x-appwrite-id` header to allow the server to know that the partial upload is for the existing file and not for a new one.\r\n\r\nIf you're creating a new file using one of the Appwrite SDKs, all the chunking logic will be managed by the SDK internally.\r\n", "responses": { "201": { "description": "File", @@ -18575,7 +18575,8 @@ "jpeg", "gif", "png", - "webp" + "webp", + "avif" ], "x-enum-name": "ImageFormat", "x-enum-keys": [], @@ -19149,7 +19150,7 @@ "tags": [ "teams" ], - "description": "Invite a new member to join your team. Provide an ID for existing users, or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team.\n\nYou only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters.\n\nUse the `url` parameter to redirect the user from the invitation email to your app. After the user is redirected, use the [Update Team Membership Status](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team. \n\nPlease note that to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console.\n", + "description": "Invite a new member to join your team. Provide an ID for existing users, or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team.\r\n\r\nYou only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters.\r\n\r\nUse the `url` parameter to redirect the user from the invitation email to your app. After the user is redirected, use the [Update Team Membership Status](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team. \r\n\r\nPlease note that to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console.\r\n", "responses": { "201": { "description": "Membership", @@ -19342,7 +19343,7 @@ "tags": [ "teams" ], - "description": "Modify the roles of a team member. Only team members with the owner role have access to this endpoint. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions).\n", + "description": "Modify the roles of a team member. Only team members with the owner role have access to this endpoint. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions).\r\n", "responses": { "200": { "description": "Membership", @@ -19508,7 +19509,7 @@ "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 received by the user.\n\nIf the request is successful, a session for the user is automatically created.\n", + "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 received by the user.\r\n\r\nIf the request is successful, a session for the user is automatically created.\r\n", "responses": { "200": { "description": "Membership", @@ -21087,7 +21088,7 @@ "tags": [ "users" ], - "description": "Update the user labels by its unique ID. \n\nLabels can be used to grant access to resources. While teams are a way for user's to share access to a resource, labels can be defined by the developer to grant access without an invitation. See the [Permissions docs](https:\/\/appwrite.io\/docs\/permissions) for more info.", + "description": "Update the user labels by its unique ID. \r\n\r\nLabels can be used to grant access to resources. While teams are a way for user's to share access to a resource, labels can be defined by the developer to grant access without an invitation. See the [Permissions docs](https:\/\/appwrite.io\/docs\/permissions) for more info.", "responses": { "200": { "description": "User", @@ -22175,7 +22176,7 @@ "tags": [ "users" ], - "description": "Creates a session for a user. Returns an immediately usable session object.\n\nIf you want to generate a token for a custom authentication flow, use the [POST \/users\/{userId}\/tokens](https:\/\/appwrite.io\/docs\/server\/users#createToken) endpoint.", + "description": "Creates a session for a user. Returns an immediately usable session object.\r\n\r\nIf you want to generate a token for a custom authentication flow, use the [POST \/users\/{userId}\/tokens](https:\/\/appwrite.io\/docs\/server\/users#createToken) endpoint.", "responses": { "201": { "description": "Session", @@ -22875,7 +22876,7 @@ "tags": [ "users" ], - "description": "Returns a token with a secret key for creating a session. Use the user ID and secret and submit a request to the [PUT \/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process.\n", + "description": "Returns a token with a secret key for creating a session. Use the user ID and secret and submit a request to the [PUT \/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process.\r\n", "responses": { "201": { "description": "Token", diff --git a/app/config/specs/swagger2-latest-client.json b/app/config/specs/swagger2-latest-client.json index cf001af0e8..fce6a871a3 100644 --- a/app/config/specs/swagger2-latest-client.json +++ b/app/config/specs/swagger2-latest-client.json @@ -222,7 +222,7 @@ "tags": [ "account" ], - "description": "Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. 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.\n", + "description": "Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. For security measures, user password is required to complete this request.\r\nThis endpoint can also be used to convert an anonymous account to a normal one, by passing an email address and a new password.\r\n", "responses": { "200": { "description": "User", @@ -1670,7 +1670,7 @@ "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](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createRecovery) 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.", + "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](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createRecovery) endpoint.\r\n\r\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", @@ -1915,7 +1915,7 @@ "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.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", + "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.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", "responses": { "201": { "description": "Session", @@ -2075,7 +2075,7 @@ "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.\n\nIf there is already an active session, the new session will be attached to the logged-in account. If there are no active sessions, the server will attempt to look for a user with the same email address as the email received from the OAuth2 provider and attach the new session to the existing user. If no matching user is found - the server will create a new user.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\n", + "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.\r\n\r\nIf there is already an active session, the new session will be attached to the logged-in account. If there are no active sessions, the server will attempt to look for a user with the same email address as the email received from the OAuth2 provider and attach the new session to the existing user. If no matching user is found - the server will create a new user.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\r\n", "responses": { "301": { "description": "No content" @@ -2836,7 +2836,7 @@ "tags": [ "account" ], - "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's email is valid for 15 minutes.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", + "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's email is valid for 15 minutes.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", "responses": { "201": { "description": "Token", @@ -2922,7 +2922,7 @@ "tags": [ "account" ], - "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. If you are on a mobile device you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\n", + "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. If you are on a mobile device you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\r\n", "responses": { "201": { "description": "Token", @@ -3017,7 +3017,7 @@ "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. \n\nIf authentication succeeds, `userId` and `secret` of a token will be appended to the success URL as query parameters. These can be used to create a new session using the [Create session](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", + "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. \r\n\r\nIf authentication succeeds, `userId` and `secret` of a token will be appended to the success URL as query parameters. These can be used to create a new session using the [Create session](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", "responses": { "301": { "description": "No content" @@ -3152,7 +3152,7 @@ "tags": [ "account" ], - "description": "Sends the user an SMS with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's phone is valid for 15 minutes.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", + "description": "Sends the user an SMS with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's phone is valid for 15 minutes.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", "responses": { "201": { "description": "Token", @@ -3235,7 +3235,7 @@ "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](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). 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", + "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](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\r\n\r\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.\r\n", "responses": { "201": { "description": "Token", @@ -3528,7 +3528,7 @@ "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 [GET \/account\/sessions](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#getSessions) endpoint. Use width, height and quality arguments to change the output settings.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.", + "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 [GET \/account\/sessions](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#getSessions) endpoint. Use width, height and quality arguments to change the output settings.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.", "responses": { "200": { "description": "Image", @@ -3657,7 +3657,7 @@ "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.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n", + "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.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n", "responses": { "200": { "description": "Image", @@ -3790,7 +3790,7 @@ "tags": [ "avatars" ], - "description": "Use this endpoint to fetch the favorite icon (AKA favicon) of any remote website URL.\n\nThis endpoint does not follow HTTP redirects.", + "description": "Use this endpoint to fetch the favorite icon (AKA favicon) of any remote website URL.\r\n\r\nThis endpoint does not follow HTTP redirects.", "responses": { "200": { "description": "Image", @@ -3857,7 +3857,7 @@ "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. Country codes follow the [ISO 3166-1](https:\/\/en.wikipedia.org\/wiki\/ISO_3166-1) standard.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n", + "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. Country codes follow the [ISO 3166-1](https:\/\/en.wikipedia.org\/wiki\/ISO_3166-1) standard.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n", "responses": { "200": { "description": "Image", @@ -4348,7 +4348,7 @@ "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.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 400x400px.\n\nThis endpoint does not follow HTTP redirects.", + "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.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 400x400px.\r\n\r\nThis endpoint does not follow HTTP redirects.", "responses": { "200": { "description": "Image", @@ -4435,7 +4435,7 @@ "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.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n", + "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.\r\n\r\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.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n", "responses": { "200": { "description": "Image", @@ -4530,7 +4530,7 @@ "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.\n", + "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.\r\n", "responses": { "200": { "description": "Image", @@ -5528,7 +5528,7 @@ "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))", + "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.\r\n\r\n([IP Geolocation by DB-IP](https:\/\/db-ip.com))", "responses": { "200": { "description": "Locale", @@ -6225,7 +6225,7 @@ "tags": [ "storage" ], - "description": "Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/storage#storageCreateBucket) API or directly from your Appwrite console.\n\nLarger files should be uploaded using multiple requests with the [content-range](https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Content-Range) header to send a partial request with a maximum supported chunk of `5MB`. The `content-range` header values should always be in bytes.\n\nWhen the first request is sent, the server will return the **File** object, and the subsequent part request must include the file's **id** in `x-appwrite-id` header to allow the server to know that the partial upload is for the existing file and not for a new one.\n\nIf you're creating a new file using one of the Appwrite SDKs, all the chunking logic will be managed by the SDK internally.\n", + "description": "Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/storage#storageCreateBucket) API or directly from your Appwrite console.\r\n\r\nLarger files should be uploaded using multiple requests with the [content-range](https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Content-Range) header to send a partial request with a maximum supported chunk of `5MB`. The `content-range` header values should always be in bytes.\r\n\r\nWhen the first request is sent, the server will return the **File** object, and the subsequent part request must include the file's **id** in `x-appwrite-id` header to allow the server to know that the partial upload is for the existing file and not for a new one.\r\n\r\nIf you're creating a new file using one of the Appwrite SDKs, all the chunking logic will be managed by the SDK internally.\r\n", "responses": { "201": { "description": "File", @@ -6807,7 +6807,8 @@ "jpeg", "gif", "png", - "webp" + "webp", + "avif" ], "x-enum-name": "ImageFormat", "x-enum-keys": [], @@ -7367,7 +7368,7 @@ "tags": [ "teams" ], - "description": "Invite a new member to join your team. Provide an ID for existing users, or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team.\n\nYou only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters.\n\nUse the `url` parameter to redirect the user from the invitation email to your app. After the user is redirected, use the [Update Team Membership Status](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team. \n\nPlease note that to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console.\n", + "description": "Invite a new member to join your team. Provide an ID for existing users, or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team.\r\n\r\nYou only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters.\r\n\r\nUse the `url` parameter to redirect the user from the invitation email to your app. After the user is redirected, use the [Update Team Membership Status](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team. \r\n\r\nPlease note that to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console.\r\n", "responses": { "201": { "description": "Membership", @@ -7556,7 +7557,7 @@ "tags": [ "teams" ], - "description": "Modify the roles of a team member. Only team members with the owner role have access to this endpoint. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions).\n", + "description": "Modify the roles of a team member. Only team members with the owner role have access to this endpoint. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions).\r\n", "responses": { "200": { "description": "Membership", @@ -7718,7 +7719,7 @@ "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 received by the user.\n\nIf the request is successful, a session for the user is automatically created.\n", + "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 received by the user.\r\n\r\nIf the request is successful, a session for the user is automatically created.\r\n", "responses": { "200": { "description": "Membership", diff --git a/app/config/specs/swagger2-latest-console.json b/app/config/specs/swagger2-latest-console.json index d9037abc55..bd2aeaa518 100644 --- a/app/config/specs/swagger2-latest-console.json +++ b/app/config/specs/swagger2-latest-console.json @@ -278,7 +278,7 @@ "tags": [ "account" ], - "description": "Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. 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.\n", + "description": "Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. For security measures, user password is required to complete this request.\r\nThis endpoint can also be used to convert an anonymous account to a normal one, by passing an email address and a new password.\r\n", "responses": { "200": { "description": "User", @@ -1707,7 +1707,7 @@ "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](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createRecovery) 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.", + "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](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createRecovery) endpoint.\r\n\r\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", @@ -1949,7 +1949,7 @@ "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.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", + "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.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", "responses": { "201": { "description": "Session", @@ -2109,7 +2109,7 @@ "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.\n\nIf there is already an active session, the new session will be attached to the logged-in account. If there are no active sessions, the server will attempt to look for a user with the same email address as the email received from the OAuth2 provider and attach the new session to the existing user. If no matching user is found - the server will create a new user.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\n", + "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.\r\n\r\nIf there is already an active session, the new session will be attached to the logged-in account. If there are no active sessions, the server will attempt to look for a user with the same email address as the email received from the OAuth2 provider and attach the new session to the existing user. If no matching user is found - the server will create a new user.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\r\n", "responses": { "301": { "description": "No content" @@ -2863,7 +2863,7 @@ "tags": [ "account" ], - "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's email is valid for 15 minutes.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", + "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's email is valid for 15 minutes.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", "responses": { "201": { "description": "Token", @@ -2949,7 +2949,7 @@ "tags": [ "account" ], - "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. If you are on a mobile device you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\n", + "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. If you are on a mobile device you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\r\n", "responses": { "201": { "description": "Token", @@ -3044,7 +3044,7 @@ "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. \n\nIf authentication succeeds, `userId` and `secret` of a token will be appended to the success URL as query parameters. These can be used to create a new session using the [Create session](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", + "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. \r\n\r\nIf authentication succeeds, `userId` and `secret` of a token will be appended to the success URL as query parameters. These can be used to create a new session using the [Create session](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", "responses": { "301": { "description": "No content" @@ -3179,7 +3179,7 @@ "tags": [ "account" ], - "description": "Sends the user an SMS with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's phone is valid for 15 minutes.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", + "description": "Sends the user an SMS with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's phone is valid for 15 minutes.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", "responses": { "201": { "description": "Token", @@ -3262,7 +3262,7 @@ "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](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). 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", + "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](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\r\n\r\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.\r\n", "responses": { "201": { "description": "Token", @@ -3551,7 +3551,7 @@ "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 [GET \/account\/sessions](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#getSessions) endpoint. Use width, height and quality arguments to change the output settings.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.", + "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 [GET \/account\/sessions](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#getSessions) endpoint. Use width, height and quality arguments to change the output settings.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.", "responses": { "200": { "description": "Image", @@ -3680,7 +3680,7 @@ "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.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n", + "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.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n", "responses": { "200": { "description": "Image", @@ -3813,7 +3813,7 @@ "tags": [ "avatars" ], - "description": "Use this endpoint to fetch the favorite icon (AKA favicon) of any remote website URL.\n\nThis endpoint does not follow HTTP redirects.", + "description": "Use this endpoint to fetch the favorite icon (AKA favicon) of any remote website URL.\r\n\r\nThis endpoint does not follow HTTP redirects.", "responses": { "200": { "description": "Image", @@ -3880,7 +3880,7 @@ "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. Country codes follow the [ISO 3166-1](https:\/\/en.wikipedia.org\/wiki\/ISO_3166-1) standard.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n", + "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. Country codes follow the [ISO 3166-1](https:\/\/en.wikipedia.org\/wiki\/ISO_3166-1) standard.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n", "responses": { "200": { "description": "Image", @@ -4371,7 +4371,7 @@ "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.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 400x400px.\n\nThis endpoint does not follow HTTP redirects.", + "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.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 400x400px.\r\n\r\nThis endpoint does not follow HTTP redirects.", "responses": { "200": { "description": "Image", @@ -4458,7 +4458,7 @@ "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.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n", + "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.\r\n\r\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.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n", "responses": { "200": { "description": "Image", @@ -4553,7 +4553,7 @@ "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.\n", + "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.\r\n", "responses": { "200": { "description": "Image", @@ -4846,7 +4846,7 @@ "tags": [ "databases" ], - "description": "Create a new Database.\n", + "description": "Create a new Database.\r\n", "responses": { "201": { "description": "Database", @@ -5727,7 +5727,7 @@ "tags": [ "databases" ], - "description": "Create a boolean attribute.\n", + "description": "Create a boolean attribute.\r\n", "responses": { "202": { "description": "AttributeBoolean", @@ -6159,7 +6159,7 @@ "tags": [ "databases" ], - "description": "Create an email attribute.\n", + "description": "Create an email attribute.\r\n", "responses": { "202": { "description": "AttributeEmail", @@ -6265,7 +6265,7 @@ "tags": [ "databases" ], - "description": "Update an email attribute. Changing the `default` value will not update already existing documents.\n", + "description": "Update an email attribute. Changing the `default` value will not update already existing documents.\r\n", "responses": { "200": { "description": "AttributeEmail", @@ -6375,7 +6375,7 @@ "tags": [ "databases" ], - "description": "Create an enumeration attribute. The `elements` param acts as a white-list of accepted values for this attribute. \n", + "description": "Create an enumeration attribute. The `elements` param acts as a white-list of accepted values for this attribute. \r\n", "responses": { "202": { "description": "AttributeEnum", @@ -6491,7 +6491,7 @@ "tags": [ "databases" ], - "description": "Update an enum attribute. Changing the `default` value will not update already existing documents.\n", + "description": "Update an enum attribute. Changing the `default` value will not update already existing documents.\r\n", "responses": { "200": { "description": "AttributeEnum", @@ -6611,7 +6611,7 @@ "tags": [ "databases" ], - "description": "Create a float attribute. Optionally, minimum and maximum values can be provided.\n", + "description": "Create a float attribute. Optionally, minimum and maximum values can be provided.\r\n", "responses": { "202": { "description": "AttributeFloat", @@ -6729,7 +6729,7 @@ "tags": [ "databases" ], - "description": "Update a float attribute. Changing the `default` value will not update already existing documents.\n", + "description": "Update a float attribute. Changing the `default` value will not update already existing documents.\r\n", "responses": { "200": { "description": "AttributeFloat", @@ -6853,7 +6853,7 @@ "tags": [ "databases" ], - "description": "Create an integer attribute. Optionally, minimum and maximum values can be provided.\n", + "description": "Create an integer attribute. Optionally, minimum and maximum values can be provided.\r\n", "responses": { "202": { "description": "AttributeInteger", @@ -6971,7 +6971,7 @@ "tags": [ "databases" ], - "description": "Update an integer attribute. Changing the `default` value will not update already existing documents.\n", + "description": "Update an integer attribute. Changing the `default` value will not update already existing documents.\r\n", "responses": { "200": { "description": "AttributeInteger", @@ -7095,7 +7095,7 @@ "tags": [ "databases" ], - "description": "Create IP address attribute.\n", + "description": "Create IP address attribute.\r\n", "responses": { "202": { "description": "AttributeIP", @@ -7201,7 +7201,7 @@ "tags": [ "databases" ], - "description": "Update an ip attribute. Changing the `default` value will not update already existing documents.\n", + "description": "Update an ip attribute. Changing the `default` value will not update already existing documents.\r\n", "responses": { "200": { "description": "AttributeIP", @@ -7311,7 +7311,7 @@ "tags": [ "databases" ], - "description": "Create relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\n", + "description": "Create relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\r\n", "responses": { "202": { "description": "AttributeRelationship", @@ -7446,7 +7446,7 @@ "tags": [ "databases" ], - "description": "Create a string attribute.\n", + "description": "Create a string attribute.\r\n", "responses": { "202": { "description": "AttributeString", @@ -7565,7 +7565,7 @@ "tags": [ "databases" ], - "description": "Update a string attribute. Changing the `default` value will not update already existing documents.\n", + "description": "Update a string attribute. Changing the `default` value will not update already existing documents.\r\n", "responses": { "200": { "description": "AttributeString", @@ -7681,7 +7681,7 @@ "tags": [ "databases" ], - "description": "Create a URL attribute.\n", + "description": "Create a URL attribute.\r\n", "responses": { "202": { "description": "AttributeURL", @@ -7787,7 +7787,7 @@ "tags": [ "databases" ], - "description": "Update an url attribute. Changing the `default` value will not update already existing documents.\n", + "description": "Update an url attribute. Changing the `default` value will not update already existing documents.\r\n", "responses": { "200": { "description": "AttributeURL", @@ -8075,7 +8075,7 @@ "tags": [ "databases" ], - "description": "Update relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\n", + "description": "Update relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\r\n", "responses": { "200": { "description": "AttributeRelationship", @@ -8817,7 +8817,7 @@ "tags": [ "databases" ], - "description": "Creates an index on the attributes listed. Your index should include all the attributes you will query in a single request.\nAttributes can be `key`, `fulltext`, and `unique`.", + "description": "Creates an index on the attributes listed. Your index should include all the attributes you will query in a single request.\r\nAttributes can be `key`, `fulltext`, and `unique`.", "responses": { "202": { "description": "Index", @@ -9806,7 +9806,7 @@ "tags": [ "functions" ], - "description": "List allowed function specifications for this instance.\n", + "description": "List allowed function specifications for this instance.\r\n", "responses": { "200": { "description": "Specifications List", @@ -10533,7 +10533,7 @@ "tags": [ "functions" ], - "description": "Create a new function code deployment. 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 deployment to use your new deployment 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](https:\/\/appwrite.io\/docs\/functions).\n\nUse the \"command\" param to set the entrypoint used to execute your code.", + "description": "Create a new function code deployment. 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 deployment to use your new deployment UID.\r\n\r\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](https:\/\/appwrite.io\/docs\/functions).\r\n\r\nUse the \"command\" param to set the entrypoint used to execute your code.", "responses": { "202": { "description": "Deployment", @@ -11336,7 +11336,7 @@ "tags": [ "functions" ], - "description": "Delete a function execution by its unique ID.\n", + "description": "Delete a function execution by its unique ID.\r\n", "responses": { "204": { "description": "No content" @@ -12660,7 +12660,7 @@ "tags": [ "health" ], - "description": "Returns the amount of failed jobs in a given queue.\n", + "description": "Returns the amount of failed jobs in a given queue.\r\n", "responses": { "200": { "description": "Health Queue", @@ -13419,7 +13419,7 @@ "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))", + "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.\r\n\r\n([IP Geolocation by DB-IP](https:\/\/db-ip.com))", "responses": { "200": { "description": "Locale", @@ -14104,7 +14104,7 @@ "tags": [ "messaging" ], - "description": "Update an email message by its unique ID.\n", + "description": "Update an email message by its unique ID.\r\n", "responses": { "200": { "description": "Message", @@ -14436,7 +14436,7 @@ "tags": [ "messaging" ], - "description": "Update a push notification by its unique ID.\n", + "description": "Update a push notification by its unique ID.\r\n", "responses": { "200": { "description": "Message", @@ -14728,7 +14728,7 @@ "tags": [ "messaging" ], - "description": "Update an email message by its unique ID.\n", + "description": "Update an email message by its unique ID.\r\n", "responses": { "200": { "description": "Message", @@ -14846,7 +14846,7 @@ "tags": [ "messaging" ], - "description": "Get a message by its unique ID.\n", + "description": "Get a message by its unique ID.\r\n", "responses": { "200": { "description": "Message", @@ -17461,7 +17461,7 @@ "tags": [ "messaging" ], - "description": "Get a provider by its unique ID.\n", + "description": "Get a provider by its unique ID.\r\n", "responses": { "200": { "description": "Provider", @@ -17903,7 +17903,7 @@ "tags": [ "messaging" ], - "description": "Get a topic by its unique ID.\n", + "description": "Get a topic by its unique ID.\r\n", "responses": { "200": { "description": "Topic", @@ -17965,7 +17965,7 @@ "tags": [ "messaging" ], - "description": "Update a topic by its unique ID.\n", + "description": "Update a topic by its unique ID.\r\n", "responses": { "200": { "description": "Topic", @@ -18363,7 +18363,7 @@ "tags": [ "messaging" ], - "description": "Get a subscriber by its unique ID.\n", + "description": "Get a subscriber by its unique ID.\r\n", "responses": { "200": { "description": "Subscriber", @@ -23101,7 +23101,8 @@ "default": "", "x-example": "tls", "enum": [ - "tls" + "tls", + "ssl" ], "x-enum-name": "SMTPSecure", "x-enum-keys": [] @@ -26014,7 +26015,7 @@ "tags": [ "storage" ], - "description": "Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/storage#storageCreateBucket) API or directly from your Appwrite console.\n\nLarger files should be uploaded using multiple requests with the [content-range](https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Content-Range) header to send a partial request with a maximum supported chunk of `5MB`. The `content-range` header values should always be in bytes.\n\nWhen the first request is sent, the server will return the **File** object, and the subsequent part request must include the file's **id** in `x-appwrite-id` header to allow the server to know that the partial upload is for the existing file and not for a new one.\n\nIf you're creating a new file using one of the Appwrite SDKs, all the chunking logic will be managed by the SDK internally.\n", + "description": "Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/storage#storageCreateBucket) API or directly from your Appwrite console.\r\n\r\nLarger files should be uploaded using multiple requests with the [content-range](https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Content-Range) header to send a partial request with a maximum supported chunk of `5MB`. The `content-range` header values should always be in bytes.\r\n\r\nWhen the first request is sent, the server will return the **File** object, and the subsequent part request must include the file's **id** in `x-appwrite-id` header to allow the server to know that the partial upload is for the existing file and not for a new one.\r\n\r\nIf you're creating a new file using one of the Appwrite SDKs, all the chunking logic will be managed by the SDK internally.\r\n", "responses": { "201": { "description": "File", @@ -26596,7 +26597,8 @@ "jpeg", "gif", "png", - "webp" + "webp", + "avif" ], "x-enum-name": "ImageFormat", "x-enum-keys": [], @@ -27386,7 +27388,7 @@ "tags": [ "teams" ], - "description": "Invite a new member to join your team. Provide an ID for existing users, or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team.\n\nYou only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters.\n\nUse the `url` parameter to redirect the user from the invitation email to your app. After the user is redirected, use the [Update Team Membership Status](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team. \n\nPlease note that to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console.\n", + "description": "Invite a new member to join your team. Provide an ID for existing users, or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team.\r\n\r\nYou only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters.\r\n\r\nUse the `url` parameter to redirect the user from the invitation email to your app. After the user is redirected, use the [Update Team Membership Status](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team. \r\n\r\nPlease note that to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console.\r\n", "responses": { "201": { "description": "Membership", @@ -27575,7 +27577,7 @@ "tags": [ "teams" ], - "description": "Modify the roles of a team member. Only team members with the owner role have access to this endpoint. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions).\n", + "description": "Modify the roles of a team member. Only team members with the owner role have access to this endpoint. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions).\r\n", "responses": { "200": { "description": "Membership", @@ -27737,7 +27739,7 @@ "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 received by the user.\n\nIf the request is successful, a session for the user is automatically created.\n", + "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 received by the user.\r\n\r\nIf the request is successful, a session for the user is automatically created.\r\n", "responses": { "200": { "description": "Membership", @@ -29369,7 +29371,7 @@ "tags": [ "users" ], - "description": "Update the user labels by its unique ID. \n\nLabels can be used to grant access to resources. While teams are a way for user's to share access to a resource, labels can be defined by the developer to grant access without an invitation. See the [Permissions docs](https:\/\/appwrite.io\/docs\/permissions) for more info.", + "description": "Update the user labels by its unique ID. \r\n\r\nLabels can be used to grant access to resources. While teams are a way for user's to share access to a resource, labels can be defined by the developer to grant access without an invitation. See the [Permissions docs](https:\/\/appwrite.io\/docs\/permissions) for more info.", "responses": { "200": { "description": "User", @@ -30442,7 +30444,7 @@ "tags": [ "users" ], - "description": "Creates a session for a user. Returns an immediately usable session object.\n\nIf you want to generate a token for a custom authentication flow, use the [POST \/users\/{userId}\/tokens](https:\/\/appwrite.io\/docs\/server\/users#createToken) endpoint.", + "description": "Creates a session for a user. Returns an immediately usable session object.\r\n\r\nIf you want to generate a token for a custom authentication flow, use the [POST \/users\/{userId}\/tokens](https:\/\/appwrite.io\/docs\/server\/users#createToken) endpoint.", "responses": { "201": { "description": "Session", @@ -31133,7 +31135,7 @@ "tags": [ "users" ], - "description": "Returns a token with a secret key for creating a session. Use the user ID and secret and submit a request to the [PUT \/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process.\n", + "description": "Returns a token with a secret key for creating a session. Use the user ID and secret and submit a request to the [PUT \/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process.\r\n", "responses": { "201": { "description": "Token", diff --git a/app/config/specs/swagger2-latest-server.json b/app/config/specs/swagger2-latest-server.json index 42cf3ff2c7..77ac485772 100644 --- a/app/config/specs/swagger2-latest-server.json +++ b/app/config/specs/swagger2-latest-server.json @@ -238,7 +238,7 @@ "tags": [ "account" ], - "description": "Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. 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.\n", + "description": "Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. For security measures, user password is required to complete this request.\r\nThis endpoint can also be used to convert an anonymous account to a normal one, by passing an email address and a new password.\r\n", "responses": { "200": { "description": "User", @@ -1705,7 +1705,7 @@ "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](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createRecovery) 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.", + "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](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createRecovery) endpoint.\r\n\r\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", @@ -1953,7 +1953,7 @@ "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.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", + "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.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", "responses": { "201": { "description": "Session", @@ -2518,7 +2518,7 @@ "tags": [ "account" ], - "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's email is valid for 15 minutes.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", + "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's email is valid for 15 minutes.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", "responses": { "201": { "description": "Token", @@ -2604,7 +2604,7 @@ "tags": [ "account" ], - "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. If you are on a mobile device you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\n", + "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. If you are on a mobile device you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\r\n", "responses": { "201": { "description": "Token", @@ -2699,7 +2699,7 @@ "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. \n\nIf authentication succeeds, `userId` and `secret` of a token will be appended to the success URL as query parameters. These can be used to create a new session using the [Create session](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", + "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. \r\n\r\nIf authentication succeeds, `userId` and `secret` of a token will be appended to the success URL as query parameters. These can be used to create a new session using the [Create session](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", "responses": { "301": { "description": "No content" @@ -2834,7 +2834,7 @@ "tags": [ "account" ], - "description": "Sends the user an SMS with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's phone is valid for 15 minutes.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", + "description": "Sends the user an SMS with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's phone is valid for 15 minutes.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", "responses": { "201": { "description": "Token", @@ -2917,7 +2917,7 @@ "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](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). 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", + "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](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\r\n\r\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.\r\n", "responses": { "201": { "description": "Token", @@ -3214,7 +3214,7 @@ "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 [GET \/account\/sessions](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#getSessions) endpoint. Use width, height and quality arguments to change the output settings.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.", + "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 [GET \/account\/sessions](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#getSessions) endpoint. Use width, height and quality arguments to change the output settings.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.", "responses": { "200": { "description": "Image", @@ -3345,7 +3345,7 @@ "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.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n", + "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.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n", "responses": { "200": { "description": "Image", @@ -3480,7 +3480,7 @@ "tags": [ "avatars" ], - "description": "Use this endpoint to fetch the favorite icon (AKA favicon) of any remote website URL.\n\nThis endpoint does not follow HTTP redirects.", + "description": "Use this endpoint to fetch the favorite icon (AKA favicon) of any remote website URL.\r\n\r\nThis endpoint does not follow HTTP redirects.", "responses": { "200": { "description": "Image", @@ -3549,7 +3549,7 @@ "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. Country codes follow the [ISO 3166-1](https:\/\/en.wikipedia.org\/wiki\/ISO_3166-1) standard.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n", + "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. Country codes follow the [ISO 3166-1](https:\/\/en.wikipedia.org\/wiki\/ISO_3166-1) standard.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n", "responses": { "200": { "description": "Image", @@ -4042,7 +4042,7 @@ "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.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 400x400px.\n\nThis endpoint does not follow HTTP redirects.", + "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.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 400x400px.\r\n\r\nThis endpoint does not follow HTTP redirects.", "responses": { "200": { "description": "Image", @@ -4131,7 +4131,7 @@ "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.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n", + "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.\r\n\r\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.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n", "responses": { "200": { "description": "Image", @@ -4228,7 +4228,7 @@ "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.\n", + "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.\r\n", "responses": { "200": { "description": "Image", @@ -4400,7 +4400,7 @@ "tags": [ "databases" ], - "description": "Create a new Database.\n", + "description": "Create a new Database.\r\n", "responses": { "201": { "description": "Database", @@ -5217,7 +5217,7 @@ "tags": [ "databases" ], - "description": "Create a boolean attribute.\n", + "description": "Create a boolean attribute.\r\n", "responses": { "202": { "description": "AttributeBoolean", @@ -5653,7 +5653,7 @@ "tags": [ "databases" ], - "description": "Create an email attribute.\n", + "description": "Create an email attribute.\r\n", "responses": { "202": { "description": "AttributeEmail", @@ -5760,7 +5760,7 @@ "tags": [ "databases" ], - "description": "Update an email attribute. Changing the `default` value will not update already existing documents.\n", + "description": "Update an email attribute. Changing the `default` value will not update already existing documents.\r\n", "responses": { "200": { "description": "AttributeEmail", @@ -5871,7 +5871,7 @@ "tags": [ "databases" ], - "description": "Create an enumeration attribute. The `elements` param acts as a white-list of accepted values for this attribute. \n", + "description": "Create an enumeration attribute. The `elements` param acts as a white-list of accepted values for this attribute. \r\n", "responses": { "202": { "description": "AttributeEnum", @@ -5988,7 +5988,7 @@ "tags": [ "databases" ], - "description": "Update an enum attribute. Changing the `default` value will not update already existing documents.\n", + "description": "Update an enum attribute. Changing the `default` value will not update already existing documents.\r\n", "responses": { "200": { "description": "AttributeEnum", @@ -6109,7 +6109,7 @@ "tags": [ "databases" ], - "description": "Create a float attribute. Optionally, minimum and maximum values can be provided.\n", + "description": "Create a float attribute. Optionally, minimum and maximum values can be provided.\r\n", "responses": { "202": { "description": "AttributeFloat", @@ -6228,7 +6228,7 @@ "tags": [ "databases" ], - "description": "Update a float attribute. Changing the `default` value will not update already existing documents.\n", + "description": "Update a float attribute. Changing the `default` value will not update already existing documents.\r\n", "responses": { "200": { "description": "AttributeFloat", @@ -6353,7 +6353,7 @@ "tags": [ "databases" ], - "description": "Create an integer attribute. Optionally, minimum and maximum values can be provided.\n", + "description": "Create an integer attribute. Optionally, minimum and maximum values can be provided.\r\n", "responses": { "202": { "description": "AttributeInteger", @@ -6472,7 +6472,7 @@ "tags": [ "databases" ], - "description": "Update an integer attribute. Changing the `default` value will not update already existing documents.\n", + "description": "Update an integer attribute. Changing the `default` value will not update already existing documents.\r\n", "responses": { "200": { "description": "AttributeInteger", @@ -6597,7 +6597,7 @@ "tags": [ "databases" ], - "description": "Create IP address attribute.\n", + "description": "Create IP address attribute.\r\n", "responses": { "202": { "description": "AttributeIP", @@ -6704,7 +6704,7 @@ "tags": [ "databases" ], - "description": "Update an ip attribute. Changing the `default` value will not update already existing documents.\n", + "description": "Update an ip attribute. Changing the `default` value will not update already existing documents.\r\n", "responses": { "200": { "description": "AttributeIP", @@ -6815,7 +6815,7 @@ "tags": [ "databases" ], - "description": "Create relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\n", + "description": "Create relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\r\n", "responses": { "202": { "description": "AttributeRelationship", @@ -6951,7 +6951,7 @@ "tags": [ "databases" ], - "description": "Create a string attribute.\n", + "description": "Create a string attribute.\r\n", "responses": { "202": { "description": "AttributeString", @@ -7071,7 +7071,7 @@ "tags": [ "databases" ], - "description": "Update a string attribute. Changing the `default` value will not update already existing documents.\n", + "description": "Update a string attribute. Changing the `default` value will not update already existing documents.\r\n", "responses": { "200": { "description": "AttributeString", @@ -7188,7 +7188,7 @@ "tags": [ "databases" ], - "description": "Create a URL attribute.\n", + "description": "Create a URL attribute.\r\n", "responses": { "202": { "description": "AttributeURL", @@ -7295,7 +7295,7 @@ "tags": [ "databases" ], - "description": "Update an url attribute. Changing the `default` value will not update already existing documents.\n", + "description": "Update an url attribute. Changing the `default` value will not update already existing documents.\r\n", "responses": { "200": { "description": "AttributeURL", @@ -7586,7 +7586,7 @@ "tags": [ "databases" ], - "description": "Update relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\n", + "description": "Update relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\r\n", "responses": { "200": { "description": "AttributeRelationship", @@ -8250,7 +8250,7 @@ "tags": [ "databases" ], - "description": "Creates an index on the attributes listed. Your index should include all the attributes you will query in a single request.\nAttributes can be `key`, `fulltext`, and `unique`.", + "description": "Creates an index on the attributes listed. Your index should include all the attributes you will query in a single request.\r\nAttributes can be `key`, `fulltext`, and `unique`.", "responses": { "202": { "description": "Index", @@ -8917,7 +8917,7 @@ "tags": [ "functions" ], - "description": "List allowed function specifications for this instance.\n", + "description": "List allowed function specifications for this instance.\r\n", "responses": { "200": { "description": "Specifications List", @@ -9415,7 +9415,7 @@ "tags": [ "functions" ], - "description": "Create a new function code deployment. 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 deployment to use your new deployment 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](https:\/\/appwrite.io\/docs\/functions).\n\nUse the \"command\" param to set the entrypoint used to execute your code.", + "description": "Create a new function code deployment. 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 deployment to use your new deployment UID.\r\n\r\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](https:\/\/appwrite.io\/docs\/functions).\r\n\r\nUse the \"command\" param to set the entrypoint used to execute your code.", "responses": { "202": { "description": "Deployment", @@ -10231,7 +10231,7 @@ "tags": [ "functions" ], - "description": "Delete a function execution by its unique ID.\n", + "description": "Delete a function execution by its unique ID.\r\n", "responses": { "204": { "description": "No content" @@ -11494,7 +11494,7 @@ "tags": [ "health" ], - "description": "Returns the amount of failed jobs in a given queue.\n", + "description": "Returns the amount of failed jobs in a given queue.\r\n", "responses": { "200": { "description": "Health Queue", @@ -12265,7 +12265,7 @@ "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))", + "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.\r\n\r\n([IP Geolocation by DB-IP](https:\/\/db-ip.com))", "responses": { "200": { "description": "Locale", @@ -12968,7 +12968,7 @@ "tags": [ "messaging" ], - "description": "Update an email message by its unique ID.\n", + "description": "Update an email message by its unique ID.\r\n", "responses": { "200": { "description": "Message", @@ -13302,7 +13302,7 @@ "tags": [ "messaging" ], - "description": "Update a push notification by its unique ID.\n", + "description": "Update a push notification by its unique ID.\r\n", "responses": { "200": { "description": "Message", @@ -13596,7 +13596,7 @@ "tags": [ "messaging" ], - "description": "Update an email message by its unique ID.\n", + "description": "Update an email message by its unique ID.\r\n", "responses": { "200": { "description": "Message", @@ -13715,7 +13715,7 @@ "tags": [ "messaging" ], - "description": "Get a message by its unique ID.\n", + "description": "Get a message by its unique ID.\r\n", "responses": { "200": { "description": "Message", @@ -16355,7 +16355,7 @@ "tags": [ "messaging" ], - "description": "Get a provider by its unique ID.\n", + "description": "Get a provider by its unique ID.\r\n", "responses": { "200": { "description": "Provider", @@ -16803,7 +16803,7 @@ "tags": [ "messaging" ], - "description": "Get a topic by its unique ID.\n", + "description": "Get a topic by its unique ID.\r\n", "responses": { "200": { "description": "Topic", @@ -16866,7 +16866,7 @@ "tags": [ "messaging" ], - "description": "Update a topic by its unique ID.\n", + "description": "Update a topic by its unique ID.\r\n", "responses": { "200": { "description": "Topic", @@ -17270,7 +17270,7 @@ "tags": [ "messaging" ], - "description": "Get a subscriber by its unique ID.\n", + "description": "Get a subscriber by its unique ID.\r\n", "responses": { "200": { "description": "Subscriber", @@ -17981,7 +17981,7 @@ "tags": [ "storage" ], - "description": "Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/storage#storageCreateBucket) API or directly from your Appwrite console.\n\nLarger files should be uploaded using multiple requests with the [content-range](https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Content-Range) header to send a partial request with a maximum supported chunk of `5MB`. The `content-range` header values should always be in bytes.\n\nWhen the first request is sent, the server will return the **File** object, and the subsequent part request must include the file's **id** in `x-appwrite-id` header to allow the server to know that the partial upload is for the existing file and not for a new one.\n\nIf you're creating a new file using one of the Appwrite SDKs, all the chunking logic will be managed by the SDK internally.\n", + "description": "Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/storage#storageCreateBucket) API or directly from your Appwrite console.\r\n\r\nLarger files should be uploaded using multiple requests with the [content-range](https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Content-Range) header to send a partial request with a maximum supported chunk of `5MB`. The `content-range` header values should always be in bytes.\r\n\r\nWhen the first request is sent, the server will return the **File** object, and the subsequent part request must include the file's **id** in `x-appwrite-id` header to allow the server to know that the partial upload is for the existing file and not for a new one.\r\n\r\nIf you're creating a new file using one of the Appwrite SDKs, all the chunking logic will be managed by the SDK internally.\r\n", "responses": { "201": { "description": "File", @@ -18575,7 +18575,8 @@ "jpeg", "gif", "png", - "webp" + "webp", + "avif" ], "x-enum-name": "ImageFormat", "x-enum-keys": [], @@ -19149,7 +19150,7 @@ "tags": [ "teams" ], - "description": "Invite a new member to join your team. Provide an ID for existing users, or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team.\n\nYou only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters.\n\nUse the `url` parameter to redirect the user from the invitation email to your app. After the user is redirected, use the [Update Team Membership Status](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team. \n\nPlease note that to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console.\n", + "description": "Invite a new member to join your team. Provide an ID for existing users, or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team.\r\n\r\nYou only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters.\r\n\r\nUse the `url` parameter to redirect the user from the invitation email to your app. After the user is redirected, use the [Update Team Membership Status](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team. \r\n\r\nPlease note that to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console.\r\n", "responses": { "201": { "description": "Membership", @@ -19342,7 +19343,7 @@ "tags": [ "teams" ], - "description": "Modify the roles of a team member. Only team members with the owner role have access to this endpoint. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions).\n", + "description": "Modify the roles of a team member. Only team members with the owner role have access to this endpoint. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions).\r\n", "responses": { "200": { "description": "Membership", @@ -19508,7 +19509,7 @@ "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 received by the user.\n\nIf the request is successful, a session for the user is automatically created.\n", + "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 received by the user.\r\n\r\nIf the request is successful, a session for the user is automatically created.\r\n", "responses": { "200": { "description": "Membership", @@ -21087,7 +21088,7 @@ "tags": [ "users" ], - "description": "Update the user labels by its unique ID. \n\nLabels can be used to grant access to resources. While teams are a way for user's to share access to a resource, labels can be defined by the developer to grant access without an invitation. See the [Permissions docs](https:\/\/appwrite.io\/docs\/permissions) for more info.", + "description": "Update the user labels by its unique ID. \r\n\r\nLabels can be used to grant access to resources. While teams are a way for user's to share access to a resource, labels can be defined by the developer to grant access without an invitation. See the [Permissions docs](https:\/\/appwrite.io\/docs\/permissions) for more info.", "responses": { "200": { "description": "User", @@ -22175,7 +22176,7 @@ "tags": [ "users" ], - "description": "Creates a session for a user. Returns an immediately usable session object.\n\nIf you want to generate a token for a custom authentication flow, use the [POST \/users\/{userId}\/tokens](https:\/\/appwrite.io\/docs\/server\/users#createToken) endpoint.", + "description": "Creates a session for a user. Returns an immediately usable session object.\r\n\r\nIf you want to generate a token for a custom authentication flow, use the [POST \/users\/{userId}\/tokens](https:\/\/appwrite.io\/docs\/server\/users#createToken) endpoint.", "responses": { "201": { "description": "Session", @@ -22875,7 +22876,7 @@ "tags": [ "users" ], - "description": "Returns a token with a secret key for creating a session. Use the user ID and secret and submit a request to the [PUT \/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process.\n", + "description": "Returns a token with a secret key for creating a session. Use the user ID and secret and submit a request to the [PUT \/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process.\r\n", "responses": { "201": { "description": "Token", diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index aafd480398..dc93361b57 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -1744,7 +1744,7 @@ App::post('/v1/projects/:projectId/smtp/tests') ->param('port', 587, new Integer(), 'SMTP server port', true) ->param('username', '', new Text(0, 0), 'SMTP server username', true) ->param('password', '', new Text(0, 0), 'SMTP server password', true) - ->param('secure', '', new WhiteList(['tls'], true), 'Does SMTP server use secure connection', true) + ->param('secure', '', new WhiteList(['tls', 'ssl'], true), 'Does SMTP server use secure connection', true) ->inject('response') ->inject('dbForConsole') ->inject('queueForMails')