From 683a691bf93ae8c04ee6b1f656ef994411be0e11 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Sun, 4 Sep 2022 22:05:30 +0200 Subject: [PATCH 01/42] ci: update github actions to use github hosted runners --- .github/workflows/tests.yml | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 2ed2b65ff..64f39013f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -4,7 +4,7 @@ on: [pull_request] jobs: tests: name: Unit & E2E - runs-on: self-hosted + runs-on: ubuntu-latest steps: - name: Checkout repository @@ -23,12 +23,11 @@ jobs: # Upstream bug causes buildkit pulls to fail so prefetch base images # https://github.com/moby/moby/issues/41864 run: | - echo "_APP_FUNCTIONS_RUNTIMES=php-8.0" >> .env docker pull composer:2.0 docker pull php:8.0-cli-alpine docker compose build --progress=plain docker compose up -d - sleep 10 + sleep 30 - name: Doctor run: docker compose exec -T appwrite doctor @@ -37,10 +36,3 @@ jobs: - name: Run Tests run: docker compose exec -T appwrite test --debug - - - name: Teardown - if: always() - run: | - docker ps -aq | xargs docker rm --force || true - docker volume prune --force || true - docker network prune --force || true \ No newline at end of file From bcd05bf5a7944b8f16c1f3f02e34a2b61d97e91c Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Sun, 4 Sep 2022 22:23:38 +0200 Subject: [PATCH 02/42] ci: force buildkit and settings --- .github/workflows/tests.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 64f39013f..265292961 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -23,9 +23,13 @@ jobs: # Upstream bug causes buildkit pulls to fail so prefetch base images # https://github.com/moby/moby/issues/41864 run: | + export COMPOSE_INTERACTIVE_NO_CLI + export DOCKER_BUILDKIT=1 + export COMPOSE_DOCKER_CLI_BUILD=1 + export BUILDKIT_PROGRESS=plain docker pull composer:2.0 docker pull php:8.0-cli-alpine - docker compose build --progress=plain + docker compose build docker compose up -d sleep 30 - name: Doctor From 6d5aec39fe0b53ac392df0aa5556d5d6ef9841bf Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Wed, 14 Sep 2022 16:44:50 +0200 Subject: [PATCH 03/42] ci: use faster compose file for ci --- .github/workflows/tests.yml | 4 +-- docker-compose.ci.yml | 55 +++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 2 deletions(-) create mode 100644 docker-compose.ci.yml diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 265292961..d5fc0dfb2 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -29,8 +29,8 @@ jobs: export BUILDKIT_PROGRESS=plain docker pull composer:2.0 docker pull php:8.0-cli-alpine - docker compose build - docker compose up -d + docker compose -f docker-compose.yml -f docker-compose.ci.yml build + docker compose -f docker-compose.yml -f docker-compose.ci.yml up -d sleep 30 - name: Doctor run: docker compose exec -T appwrite doctor diff --git a/docker-compose.ci.yml b/docker-compose.ci.yml new file mode 100644 index 000000000..5ae38c5dd --- /dev/null +++ b/docker-compose.ci.yml @@ -0,0 +1,55 @@ +services: + appwrite: + container_name: appwrite + image: appwrite-dev + build: + context: . + args: + - DEBUG=false + - TESTING=true + - VERSION=dev + + appwrite-realtime: + image: appwrite-dev + + appwrite-worker-audits: + image: appwrite-dev + + appwrite-worker-webhooks: + image: appwrite-dev + + appwrite-worker-deletes: + image: appwrite-dev + + appwrite-worker-databases: + image: appwrite-dev + + appwrite-worker-builds: + image: appwrite-dev + + appwrite-worker-certificates: + image: appwrite-dev + + appwrite-worker-functions: + image: appwrite-dev + + appwrite-executor: + image: appwrite-dev + + appwrite-worker-mails: + image: appwrite-dev + + appwrite-worker-messaging: + image: appwrite-dev + + appwrite-maintenance: + image: appwrite-dev + + appwrite-usage-timeseries: + image: appwrite-dev + + appwrite-usage-database: + image: appwrite-dev + + appwrite-schedule: + image: appwrite-dev From 5fd0de842fdbfc22898f12904b1c22a82d30c54e Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Wed, 14 Sep 2022 17:52:52 +0200 Subject: [PATCH 04/42] ci: fix build args --- .github/workflows/tests.yml | 2 +- docker-compose.ci.yml | 7 ------- docker-compose.yml | 6 +++--- 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d5fc0dfb2..8dfae35cd 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -29,7 +29,7 @@ jobs: export BUILDKIT_PROGRESS=plain docker pull composer:2.0 docker pull php:8.0-cli-alpine - docker compose -f docker-compose.yml -f docker-compose.ci.yml build + docker compose -f docker-compose.yml -f docker-compose.ci.yml build appwrite docker compose -f docker-compose.yml -f docker-compose.ci.yml up -d sleep 30 - name: Doctor diff --git a/docker-compose.ci.yml b/docker-compose.ci.yml index 5ae38c5dd..21d5e5d29 100644 --- a/docker-compose.ci.yml +++ b/docker-compose.ci.yml @@ -1,13 +1,6 @@ services: appwrite: - container_name: appwrite image: appwrite-dev - build: - context: . - args: - - DEBUG=false - - TESTING=true - - VERSION=dev appwrite-realtime: image: appwrite-dev diff --git a/docker-compose.yml b/docker-compose.yml index 9dd5b8721..7b4970f2e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -73,9 +73,9 @@ services: build: context: . args: - - DEBUG=false - - TESTING=true - - VERSION=dev + DEBUG: false + TESTING: true + VERSION: dev ports: - 9501:80 networks: From 0e2e7395a934b33ece76c7103e6e9614669b1dc3 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Wed, 14 Sep 2022 19:17:03 +0200 Subject: [PATCH 05/42] ci: increase test timeouts --- .../Functions/FunctionsCustomClientTest.php | 12 ++++---- .../Functions/FunctionsCustomServerTest.php | 30 +++++++++---------- .../Webhooks/WebhooksCustomServerTest.php | 2 +- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php index 6a0d87c21..56b72e446 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php @@ -116,7 +116,7 @@ class FunctionsCustomClientTest extends Scope $this->assertEquals(202, $deployment['headers']['status-code']); // Wait for deployment to be built. - sleep(10); + sleep(20); $function = $this->client->call(Client::METHOD_PATCH, '/functions/' . $function['body']['$id'] . '/deployments/' . $deploymentId, [ 'content-type' => 'application/json', @@ -228,7 +228,7 @@ class FunctionsCustomClientTest extends Scope $deploymentId = $deployment['body']['$id'] ?? ''; // Wait for deployment to be built. - sleep(10); + sleep(20); $this->assertEquals(202, $deployment['headers']['status-code']); @@ -252,7 +252,7 @@ class FunctionsCustomClientTest extends Scope $executionId = $execution['body']['$id'] ?? ''; - sleep(10); + sleep(20); $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions/' . $executionId, [ 'content-type' => 'application/json', @@ -326,7 +326,7 @@ class FunctionsCustomClientTest extends Scope $deploymentId = $deployment['body']['$id'] ?? ''; // Wait for deployment to be built. - sleep(10); + sleep(20); $this->assertEquals(202, $deployment['headers']['status-code']); @@ -395,7 +395,7 @@ class FunctionsCustomClientTest extends Scope $this->assertEquals(202, $execution['headers']['status-code']); - sleep(10); + sleep(20); $base = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', [ 'content-type' => 'application/json', @@ -556,7 +556,7 @@ class FunctionsCustomClientTest extends Scope $this->assertEquals(202, $deployment['headers']['status-code']); // Wait for deployment to be built. - sleep(10); + sleep(20); $function = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, [ 'content-type' => 'application/json', diff --git a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php index 94f7e84f4..5e498368e 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php @@ -373,7 +373,7 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals('index.php', $deployment['body']['entrypoint']); // Wait for deployment to build. - sleep(30); + sleep(60); return array_merge($data, ['deploymentId' => $deploymentId]); } @@ -610,7 +610,7 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals('', $execution['body']['stderr']); $this->assertEquals(0, $execution['body']['duration']); - sleep(5); + sleep(10); $execution = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/executions/' . $executionId, array_merge([ 'content-type' => 'application/json', @@ -637,7 +637,7 @@ class FunctionsCustomServerTest extends Scope * Test for FAILURE */ - sleep(10); + sleep(20); return array_merge($data, ['executionId' => $executionId]); } @@ -881,7 +881,7 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals(202, $deployment['headers']['status-code']); // Allow build step to run - sleep(20); + sleep(40); $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([ 'content-type' => 'application/json', @@ -894,7 +894,7 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals(202, $execution['headers']['status-code']); - sleep(10); + sleep(20); $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', array_merge([ 'content-type' => 'application/json', @@ -965,7 +965,7 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals(202, $deployment['headers']['status-code']); // Allow build step to run - sleep(10); + sleep(20); $deployment = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, array_merge([ 'content-type' => 'application/json', @@ -988,7 +988,7 @@ class FunctionsCustomServerTest extends Scope $executionId = $execution['body']['$id'] ?? ''; - sleep(10); + sleep(20); $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions/' . $executionId, array_merge([ 'content-type' => 'application/json', @@ -1087,7 +1087,7 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals(202, $deployment['headers']['status-code']); // Allow build step to run - sleep(10); + sleep(20); $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([ 'content-type' => 'application/json', @@ -1103,7 +1103,7 @@ class FunctionsCustomServerTest extends Scope $executionId = $execution['body']['$id'] ?? ''; - sleep(10); + sleep(20); $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions/' . $executionId, array_merge([ 'content-type' => 'application/json', @@ -1200,7 +1200,7 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals(202, $deployment['headers']['status-code']); // Allow build step to run - sleep(30); + sleep(60); $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([ 'content-type' => 'application/json', @@ -1216,7 +1216,7 @@ class FunctionsCustomServerTest extends Scope $executionId = $execution['body']['$id'] ?? ''; - sleep(30); + sleep(60); $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions/' . $executionId, array_merge([ 'content-type' => 'application/json', @@ -1314,7 +1314,7 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals(202, $deployment['headers']['status-code']); // Allow build step to run - sleep(40); + sleep(80); $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([ 'content-type' => 'application/json', @@ -1330,7 +1330,7 @@ class FunctionsCustomServerTest extends Scope $executionId = $execution['body']['$id'] ?? ''; - sleep(10); + sleep(20); $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions/' . $executionId, array_merge([ 'content-type' => 'application/json', @@ -1428,7 +1428,7 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals(202, $deployment['headers']['status-code']); // Allow build step to run - sleep(30); + sleep(60); $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([ 'content-type' => 'application/json', @@ -1444,7 +1444,7 @@ class FunctionsCustomServerTest extends Scope $executionId = $execution['body']['$id'] ?? ''; - sleep(10); + sleep(20); $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions/' . $executionId, array_merge([ 'content-type' => 'application/json', diff --git a/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php b/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php index db520bf50..f2f4b2726 100644 --- a/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php +++ b/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php @@ -634,7 +634,7 @@ class WebhooksCustomServerTest extends Scope $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); // wait for timeout function to complete - sleep(10); + sleep(20); $webhook = $this->getLastRequest(); $signatureExpected = self::getWebhookSignature($webhook, $this->getProject()['signatureKey']); From 00a49293012a495d93721e2cc785dc393c5ef03f Mon Sep 17 00:00:00 2001 From: Steven Nguyen Date: Thu, 15 Sep 2022 23:48:09 +0000 Subject: [PATCH 06/42] Fix date format for usage APIs --- app/controllers/api/databases.php | 6 +-- app/controllers/api/functions.php | 4 +- app/controllers/api/projects.php | 2 +- app/controllers/api/storage.php | 4 +- app/controllers/api/users.php | 2 +- tests/e2e/General/UsageTest.php | 63 ++++++++++++++++++++++++++++++- 6 files changed, 71 insertions(+), 10 deletions(-) diff --git a/app/controllers/api/databases.php b/app/controllers/api/databases.php index e2acb3077..2297f3783 100644 --- a/app/controllers/api/databases.php +++ b/app/controllers/api/databases.php @@ -2534,7 +2534,7 @@ App::get('/v1/databases/usage') }; $stats[$metric][] = [ 'value' => 0, - 'date' => DateTime::addSeconds(new \DateTime($stats[$metric][$last]['date'] ?? null), -1 * $diff), + 'date' => DateTime::formatTz(DateTime::addSeconds(new \DateTime($stats[$metric][$last]['date'] ?? null), -1 * $diff)), ]; $backfill--; } @@ -2648,7 +2648,7 @@ App::get('/v1/databases/:databaseId/usage') }; $stats[$metric][] = [ 'value' => 0, - 'date' => DateTime::addSeconds(new \DateTime($stats[$metric][$last]['date'] ?? null), -1 * $diff), + 'date' => DateTime::formatTz(DateTime::addSeconds(new \DateTime($stats[$metric][$last]['date'] ?? null), -1 * $diff)), ]; $backfill--; } @@ -2763,7 +2763,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/usage') }; $stats[$metric][] = [ 'value' => 0, - 'date' => DateTime::addSeconds(new \DateTime($stats[$metric][$last]['date'] ?? null), -1 * $diff), + 'date' => DateTime::formatTz(DateTime::addSeconds(new \DateTime($stats[$metric][$last]['date'] ?? null), -1 * $diff)), ]; $backfill--; } diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index f42d1059e..03650ad74 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -281,7 +281,7 @@ App::get('/v1/functions/:functionId/usage') }; $stats[$metric][] = [ 'value' => 0, - 'date' => DateTime::addSeconds(new \DateTime($stats[$metric][$last]['date'] ?? null), -1 * $diff), + 'date' => DateTime::formatTz(DateTime::addSeconds(new \DateTime($stats[$metric][$last]['date'] ?? null), -1 * $diff)), ]; $backfill--; } @@ -384,7 +384,7 @@ App::get('/v1/functions/usage') }; $stats[$metric][] = [ 'value' => 0, - 'date' => DateTime::addSeconds(new \DateTime($stats[$metric][$last]['date'] ?? null), -1 * $diff), + 'date' => DateTime::formatTz(DateTime::addSeconds(new \DateTime($stats[$metric][$last]['date'] ?? null), -1 * $diff)), ]; $backfill--; } diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index 5c8ba8a26..3751a3d62 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -330,7 +330,7 @@ App::get('/v1/projects/:projectId/usage') }; $stats[$metric][] = [ 'value' => 0, - 'date' => DateTime::addSeconds(new \DateTime($stats[$metric][$last]['date'] ?? null), -1 * $diff), + 'date' => DateTime::formatTz(DateTime::addSeconds(new \DateTime($stats[$metric][$last]['date'] ?? null), -1 * $diff)), ]; $backfill--; } diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index 1134887fb..ac66a60d2 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -1518,7 +1518,7 @@ App::get('/v1/storage/usage') }; $stats[$metric][] = [ 'value' => 0, - 'date' => DateTime::addSeconds(new \DateTime($stats[$metric][$last]['date'] ?? null), -1 * $diff), + 'date' => DateTime::formatTz(DateTime::addSeconds(new \DateTime($stats[$metric][$last]['date'] ?? null), -1 * $diff)), ]; $backfill--; } @@ -1629,7 +1629,7 @@ App::get('/v1/storage/:bucketId/usage') }; $stats[$metric][] = [ 'value' => 0, - 'date' => DateTime::addSeconds(new \DateTime($stats[$metric][$last]['date'] ?? null), -1 * $diff), + 'date' => DateTime::formatTz(DateTime::addSeconds(new \DateTime($stats[$metric][$last]['date'] ?? null), -1 * $diff)), ]; $backfill--; } diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index c83f03d96..ef1ccc5e5 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -1176,7 +1176,7 @@ App::get('/v1/users/usage') }; $stats[$metric][] = [ 'value' => 0, - 'date' => DateTime::addSeconds(new \DateTime($stats[$metric][$last]['date'] ?? null), -1 * $diff), + 'date' => DateTime::formatTz(DateTime::addSeconds(new \DateTime($stats[$metric][$last]['date'] ?? null), -1 * $diff)), ]; $backfill--; } diff --git a/tests/e2e/General/UsageTest.php b/tests/e2e/General/UsageTest.php index 5126b8eb8..0c42c42ff 100644 --- a/tests/e2e/General/UsageTest.php +++ b/tests/e2e/General/UsageTest.php @@ -26,6 +26,15 @@ class UsageTest extends Scope parent::setUp(); } + protected static string $formatTz = 'Y-m-d\TH:i:s.vP'; + + protected function validateDates(array $metrics): void + { + foreach ($metrics as $metric) { + $this->assertIsObject(\DateTime::createFromFormat("Y-m-d\TH:i:s.vP", $metric['date'])); + } + } + public function testPrepareUsersStats(): array { $project = $this->getProject(true); @@ -97,7 +106,9 @@ class UsageTest extends Scope $this->assertEquals(30, count($res['requests'])); $this->assertEquals(30, count($res['users'])); $this->assertEquals($usersCount, $res['users'][array_key_last($res['users'])]['value']); + $this->validateDates($res['users']); $this->assertEquals($requestsCount, $res['requests'][array_key_last($res['requests'])]['value']); + $this->validateDates($res['requests']); $res = $this->client->call(Client::METHOD_GET, '/users/usage?range=30d', array_merge($cheaders, [ 'x-appwrite-project' => $projectId, @@ -105,8 +116,11 @@ class UsageTest extends Scope ])); $res = $res['body']; $this->assertEquals(10, $res['usersCreate'][array_key_last($res['usersCreate'])]['value']); + $this->validateDates($res['usersCreate']); $this->assertEquals(5, $res['usersRead'][array_key_last($res['usersRead'])]['value']); + $this->validateDates($res['usersRead']); $this->assertEquals(5, $res['usersDelete'][array_key_last($res['usersDelete'])]['value']); + $this->validateDates($res['usersDelete']); return ['projectId' => $projectId, 'headers' => $headers, 'requestsCount' => $requestsCount]; } @@ -176,7 +190,7 @@ class UsageTest extends Scope 'path' => realpath(__DIR__ . '/../../resources/disk-a/kitten-1.jpg'), 'name' => 'kitten-1.jpg', ], - ]; + ]; for ($i = 0; $i < 10; $i++) { $file = $files[$i % count($files)]; @@ -257,7 +271,9 @@ class UsageTest extends Scope $this->assertEquals(30, count($res['requests'])); $this->assertEquals(30, count($res['storage'])); $this->assertEquals($requestsCount, $res['requests'][array_key_last($res['requests'])]['value']); + $this->validateDates($res['requests']); $this->assertEquals($storageTotal, $res['storage'][array_key_last($res['storage'])]['value']); + $this->validateDates($res['storage']); $res = $this->client->call(Client::METHOD_GET, '/storage/usage?range=30d', array_merge($headers, [ 'x-appwrite-project' => $projectId, @@ -265,14 +281,23 @@ class UsageTest extends Scope ])); $res = $res['body']; $this->assertEquals($storageTotal, $res['storage'][array_key_last($res['storage'])]['value']); + $this->validateDates($res['storage']); $this->assertEquals($bucketsCount, $res['bucketsCount'][array_key_last($res['bucketsCount'])]['value']); + $this->validateDates($res['bucketsCount']); $this->assertEquals($bucketsRead, $res['bucketsRead'][array_key_last($res['bucketsRead'])]['value']); + $this->validateDates($res['bucketsRead']); $this->assertEquals($bucketsCreate, $res['bucketsCreate'][array_key_last($res['bucketsCreate'])]['value']); + $this->validateDates($res['bucketsCreate']); $this->assertEquals($bucketsDelete, $res['bucketsDelete'][array_key_last($res['bucketsDelete'])]['value']); + $this->validateDates($res['bucketsDelete']); $this->assertEquals($filesCount, $res['filesCount'][array_key_last($res['filesCount'])]['value']); + $this->validateDates($res['filesCount']); $this->assertEquals($filesRead, $res['filesRead'][array_key_last($res['filesRead'])]['value']); + $this->validateDates($res['filesRead']); $this->assertEquals($filesCreate, $res['filesCreate'][array_key_last($res['filesCreate'])]['value']); + $this->validateDates($res['filesCreate']); $this->assertEquals($filesDelete, $res['filesDelete'][array_key_last($res['filesDelete'])]['value']); + $this->validateDates($res['filesDelete']); $res = $this->client->call(Client::METHOD_GET, '/storage/' . $bucketId . '/usage?range=30d', array_merge($headers, [ 'x-appwrite-project' => $projectId, @@ -483,8 +508,11 @@ class UsageTest extends Scope $this->assertEquals(30, count($res['requests'])); $this->assertEquals(30, count($res['storage'])); $this->assertEquals($requestsCount, $res['requests'][array_key_last($res['requests'])]['value']); + $this->validateDates($res['requests']); $this->assertEquals($collectionsCount, $res['collections'][array_key_last($res['collections'])]['value']); + $this->validateDates($res['collections']); $this->assertEquals($documentsCount, $res['documents'][array_key_last($res['documents'])]['value']); + $this->validateDates($res['documents']); $res = $this->client->call(Client::METHOD_GET, '/databases/usage?range=30d', array_merge($headers, [ 'x-appwrite-project' => $projectId, @@ -492,21 +520,34 @@ class UsageTest extends Scope ])); $res = $res['body']; $this->assertEquals($databasesCount, $res['databasesCount'][array_key_last($res['databasesCount'])]['value']); + $this->validateDates($res['databasesCount']); $this->assertEquals($collectionsCount, $res['collectionsCount'][array_key_last($res['collectionsCount'])]['value']); + $this->validateDates($res['collectionsCount']); $this->assertEquals($documentsCount, $res['documentsCount'][array_key_last($res['documentsCount'])]['value']); + $this->validateDates($res['documentsCount']); $this->assertEquals($databasesCreate, $res['databasesCreate'][array_key_last($res['databasesCreate'])]['value']); + $this->validateDates($res['databasesCreate']); $this->assertEquals($databasesRead, $res['databasesRead'][array_key_last($res['databasesRead'])]['value']); + $this->validateDates($res['databasesRead']); $this->assertEquals($databasesDelete, $res['databasesDelete'][array_key_last($res['databasesDelete'])]['value']); + $this->validateDates($res['databasesDelete']); $this->assertEquals($collectionsCreate, $res['collectionsCreate'][array_key_last($res['collectionsCreate'])]['value']); + $this->validateDates($res['collectionsCreate']); $this->assertEquals($collectionsRead, $res['collectionsRead'][array_key_last($res['collectionsRead'])]['value']); + $this->validateDates($res['collectionsRead']); $this->assertEquals($collectionsUpdate, $res['collectionsUpdate'][array_key_last($res['collectionsUpdate'])]['value']); + $this->validateDates($res['collectionsUpdate']); $this->assertEquals($collectionsDelete, $res['collectionsDelete'][array_key_last($res['collectionsDelete'])]['value']); + $this->validateDates($res['collectionsDelete']); $this->assertEquals($documentsCreate, $res['documentsCreate'][array_key_last($res['documentsCreate'])]['value']); + $this->validateDates($res['documentsCreate']); $this->assertEquals($documentsRead, $res['documentsRead'][array_key_last($res['documentsRead'])]['value']); + $this->validateDates($res['documentsRead']); $this->assertEquals($documentsDelete, $res['documentsDelete'][array_key_last($res['documentsDelete'])]['value']); + $this->validateDates($res['documentsDelete']); $res = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/usage?range=30d', array_merge($headers, [ 'x-appwrite-project' => $projectId, @@ -514,16 +555,25 @@ class UsageTest extends Scope ])); $res = $res['body']; $this->assertEquals($collectionsCount, $res['collectionsCount'][array_key_last($res['collectionsCount'])]['value']); + $this->validateDates($res['collectionsCount']); $this->assertEquals($documentsCount, $res['documentsCount'][array_key_last($res['documentsCount'])]['value']); + $this->validateDates($res['documentsCount']); $this->assertEquals($collectionsCreate, $res['collectionsCreate'][array_key_last($res['collectionsCreate'])]['value']); + $this->validateDates($res['collectionsCreate']); $this->assertEquals($collectionsRead, $res['collectionsRead'][array_key_last($res['collectionsRead'])]['value']); + $this->validateDates($res['collectionsRead']); $this->assertEquals($collectionsUpdate, $res['collectionsUpdate'][array_key_last($res['collectionsUpdate'])]['value']); + $this->validateDates($res['collectionsUpdate']); $this->assertEquals($collectionsDelete, $res['collectionsDelete'][array_key_last($res['collectionsDelete'])]['value']); + $this->validateDates($res['collectionsDelete']); $this->assertEquals($documentsCreate, $res['documentsCreate'][array_key_last($res['documentsCreate'])]['value']); + $this->validateDates($res['documentsCreate']); $this->assertEquals($documentsRead, $res['documentsRead'][array_key_last($res['documentsRead'])]['value']); + $this->validateDates($res['documentsRead']); $this->assertEquals($documentsDelete, $res['documentsDelete'][array_key_last($res['documentsDelete'])]['value']); + $this->validateDates($res['documentsDelete']); $res = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $collectionId . '/usage?range=30d', array_merge($headers, [ 'x-appwrite-project' => $projectId, @@ -531,10 +581,14 @@ class UsageTest extends Scope ])); $res = $res['body']; $this->assertEquals($documentsCount, $res['documentsCount'][array_key_last($res['documentsCount'])]['value']); + $this->validateDates($res['documentsCount']); $this->assertEquals($documentsCreate, $res['documentsCreate'][array_key_last($res['documentsCreate'])]['value']); + $this->validateDates($res['documentsCreate']); $this->assertEquals($documentsRead, $res['documentsRead'][array_key_last($res['documentsRead'])]['value']); + $this->validateDates($res['documentsRead']); $this->assertEquals($documentsDelete, $res['documentsDelete'][array_key_last($res['documentsDelete'])]['value']); + $this->validateDates($res['documentsDelete']); $data['requestsCount'] = $requestsCount; return $data; @@ -667,8 +721,11 @@ class UsageTest extends Scope $response = $response['body']; $this->assertEquals($executions, $response['executionsTotal'][array_key_last($response['executionsTotal'])]['value']); + $this->validateDates($response['executionsTotal']); $this->assertEquals($executionTime, $response['executionsTime'][array_key_last($response['executionsTime'])]['value']); + $this->validateDates($response['executionsTime']); $this->assertEquals($failures, $response['executionsFailure'][array_key_last($response['executionsFailure'])]['value']); + $this->validateDates($response['executionsFailure']); $response = $this->client->call(Client::METHOD_GET, '/functions/usage', $headers, [ 'range' => '30d' @@ -688,9 +745,13 @@ class UsageTest extends Scope $response = $response['body']; $this->assertEquals($executions, $response['executionsTotal'][array_key_last($response['executionsTotal'])]['value']); + $this->validateDates($response['executionsTotal']); $this->assertEquals($executionTime, $response['executionsTime'][array_key_last($response['executionsTime'])]['value']); + $this->validateDates($response['executionsTime']); $this->assertGreaterThan(0, $response['buildsTime'][array_key_last($response['buildsTime'])]['value']); + $this->validateDates($response['buildsTime']); $this->assertEquals($failures, $response['executionsFailure'][array_key_last($response['executionsFailure'])]['value']); + $this->validateDates($response['executionsFailure']); } protected function tearDown(): void From 81ddc99f7b88e6223ef6d29f1c13b9958499e78a Mon Sep 17 00:00:00 2001 From: Matej Baco Date: Fri, 16 Sep 2022 11:54:59 +0200 Subject: [PATCH 07/42] Improve mail events code quality --- app/workers/builds.php | 2 ++ app/workers/certificates.php | 27 ++++++++++++++------------- app/workers/mails.php | 13 +++++++------ 3 files changed, 23 insertions(+), 19 deletions(-) diff --git a/app/workers/builds.php b/app/workers/builds.php index 3d78aeb65..b2fac6710 100644 --- a/app/workers/builds.php +++ b/app/workers/builds.php @@ -109,6 +109,8 @@ class BuildsV1 extends Worker /** Trigger Webhook */ $deploymentModel = new Deployment(); + + // TODO: Use Webhook() event here. If doesnt exist, create class for it $deploymentUpdate = new Event(Event::WEBHOOK_QUEUE_NAME, Event::WEBHOOK_CLASS_NAME); $deploymentUpdate ->setProject($project) diff --git a/app/workers/certificates.php b/app/workers/certificates.php index f932ba4bc..e7885bc66 100644 --- a/app/workers/certificates.php +++ b/app/workers/certificates.php @@ -1,6 +1,7 @@ 'console', - 'project' => 'console', - 'name' => 'Appwrite Administrator', - 'recipient' => App::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS'), - 'url' => 'https://' . $domain, - 'locale' => App::getEnv('_APP_LOCALE', 'en'), - 'type' => MAIL_TYPE_CERTIFICATE, - - 'domain' => $domain, - 'error' => $errorMessage, - 'attempt' => $attempt - ]); + $mail = new Mail(); + $mail + ->setType(MAIL_TYPE_CERTIFICATE) + ->setRecipient(App::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS')) + ->setUrl('https://' . $domain) + ->setLocale(App::getEnv('_APP_LOCALE', 'en')) + ->setName('Appwrite Administrator') + ->setPayload([ + 'domain' => $domain, + 'error' => $errorMessage, + 'attempt' => $attempt + ]) + ->trigger(); } /** diff --git a/app/workers/mails.php b/app/workers/mails.php index 9b81353f0..90e9e9a66 100644 --- a/app/workers/mails.php +++ b/app/workers/mails.php @@ -32,9 +32,10 @@ class MailsV1 extends Worker return; } - $project = new Document($this->args['project']); + $project = new Document($this->args['project'] ?? []); $user = new Document($this->args['user'] ?? []); $team = new Document($this->args['team'] ?? []); + $payload = $this->args['payload'] ?? []; $recipient = $this->args['recipient']; $url = $this->args['url']; @@ -42,20 +43,20 @@ class MailsV1 extends Worker $type = $this->args['type']; $prefix = $this->getPrefix($type); $locale = new Locale($this->args['locale']); - $projectName = $project->getAttribute('name', '[APP-NAME]'); + $projectName = $project->isEmpty() ? 'Console' : $project->getAttribute('name', '[APP-NAME]'); if (!$this->doesLocaleExist($locale, $prefix)) { $locale->setDefault('en'); } - $from = $project->getId() === 'console' ? '' : \sprintf($locale->getText('emails.sender'), $projectName); + $from = $project->isEmpty() || $project->getId() === 'console' ? '' : \sprintf($locale->getText('emails.sender'), $projectName); $body = Template::fromFile(__DIR__ . '/../config/locale/templates/email-base.tpl'); $subject = ''; switch ($type) { case MAIL_TYPE_CERTIFICATE: - $domain = $this->args['domain']; - $error = $this->args['error']; - $attempt = $this->args['attempt']; + $domain = $payload['domain']; + $error = $payload['error']; + $attempt = $payload['attempt']; $subject = \sprintf($locale->getText("$prefix.subject"), $domain); $body->setParam('{{domain}}', $domain); From 12b6e671b01d2f7235271549194a4a4780661b89 Mon Sep 17 00:00:00 2001 From: Faizan Pasha Date: Sat, 17 Sep 2022 05:58:14 +0530 Subject: [PATCH 08/42] Fixed the bug #3859 --- app/workers/messaging.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/workers/messaging.php b/app/workers/messaging.php index 1fc5deaf4..f664c6b9f 100644 --- a/app/workers/messaging.php +++ b/app/workers/messaging.php @@ -9,6 +9,7 @@ use Appwrite\SMS\Adapter\Msg91; use Appwrite\SMS\Adapter\Vonage; use Appwrite\DSN\DSN; use Appwrite\Resque\Worker; +use Appwrite\SMS\Adapter; use Utopia\App; use Utopia\CLI\Console; @@ -19,7 +20,7 @@ Console::success(APP_NAME . ' messaging worker v1 has started' . "\n"); class MessagingV1 extends Worker { - protected ?SMS $sms = null; + protected ?Adapter $sms = null; protected ?string $from = null; public function getName(): string From ffe525c5ccd6e9ec5e07cd5ef8fb0d0b8e7037d2 Mon Sep 17 00:00:00 2001 From: Faizan Pasha Date: Sat, 17 Sep 2022 06:02:11 +0530 Subject: [PATCH 09/42] Removed unused import --- app/workers/messaging.php | 1 - 1 file changed, 1 deletion(-) diff --git a/app/workers/messaging.php b/app/workers/messaging.php index f664c6b9f..e8261f703 100644 --- a/app/workers/messaging.php +++ b/app/workers/messaging.php @@ -1,6 +1,5 @@ Date: Mon, 19 Sep 2022 08:09:48 +0000 Subject: [PATCH 10/42] Update sms mock to use request catcher --- .env | 2 +- app/controllers/api/account.php | 5 ++-- app/init.php | 2 +- app/workers/messaging.php | 6 ++--- composer.lock | 12 ++++----- src/Appwrite/SMS/Adapter/Mock.php | 18 +++++++++++-- .../Account/AccountCustomClientTest.php | 27 ++++++++++++++++--- 7 files changed, 52 insertions(+), 20 deletions(-) diff --git a/.env b/.env index 227ea1067..65fb54cb0 100644 --- a/.env +++ b/.env @@ -56,7 +56,7 @@ _APP_SMTP_PORT=1025 _APP_SMTP_SECURE= _APP_SMTP_USERNAME= _APP_SMTP_PASSWORD= -_APP_SMS_PROVIDER=sms://mock +_APP_SMS_PROVIDER=sms://username:password@mock _APP_SMS_FROM=+123456789 _APP_STORAGE_LIMIT=30000000 _APP_STORAGE_PREVIEW_LIMIT=20000000 diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index ef94b251c..207926341 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -2,7 +2,6 @@ use Ahc\Jwt\JWT; use Appwrite\Auth\Auth; -use Appwrite\SMS\Adapter\Mock; use Appwrite\Auth\Validator\Password; use Appwrite\Auth\Validator\Phone; use Appwrite\Detector\Detector; @@ -930,7 +929,7 @@ App::post('/v1/account/sessions/phone') ]))); } - $secret = (App::getEnv('_APP_SMS_PROVIDER') === 'sms://mock') ? Mock::$digits : Auth::codeGenerator(); + $secret = Auth::codeGenerator(); $expire = DateTime::addSeconds(new \DateTime(), Auth::TOKEN_EXPIRATION_PHONE); $token = new Document([ @@ -2258,7 +2257,7 @@ App::post('/v1/account/verification/phone') $isPrivilegedUser = Auth::isPrivilegedUser($roles); $isAppUser = Auth::isAppUser($roles); $verificationSecret = Auth::tokenGenerator(); - $secret = (App::getEnv('_APP_SMS_PROVIDER') === 'sms://mock') ? Mock::$digits : Auth::codeGenerator(); + $secret = Auth::codeGenerator(); $expire = DateTime::addSeconds(new \DateTime(), Auth::TOKEN_EXPIRATION_CONFIRM); $verification = new Document([ diff --git a/app/init.php b/app/init.php index 3c9f785f9..2e1ca7a49 100644 --- a/app/init.php +++ b/app/init.php @@ -1028,7 +1028,7 @@ App::setResource('sms', function () { $secret = $dsn->getPassword(); return match ($dsn->getHost()) { - 'mock' => new Mock('', ''), // used for tests + 'mock' => new Mock($user, $secret), // used for tests 'twilio' => new Twilio($user, $secret), 'text-magic' => new TextMagic($user, $secret), 'telesign' => new Telesign($user, $secret), diff --git a/app/workers/messaging.php b/app/workers/messaging.php index 1fc5deaf4..afd99faa8 100644 --- a/app/workers/messaging.php +++ b/app/workers/messaging.php @@ -1,6 +1,6 @@ getPassword(); $this->sms = match ($dsn->getHost()) { - 'mock' => new Mock('', ''), // used for tests + 'mock' => new Mock($user, $secret), // used for tests 'twilio' => new Twilio($user, $secret), 'text-magic' => new TextMagic($user, $secret), 'telesign' => new Telesign($user, $secret), diff --git a/composer.lock b/composer.lock index 532e73ddb..3f8d4bc04 100644 --- a/composer.lock +++ b/composer.lock @@ -4124,16 +4124,16 @@ }, { "name": "sebastian/comparator", - "version": "4.0.7", + "version": "4.0.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "7fa545db548c90bdebeb9da0583001a252be5578" + "reference": "fa0f136dd2334583309d32b62544682ee972b51a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/7fa545db548c90bdebeb9da0583001a252be5578", - "reference": "7fa545db548c90bdebeb9da0583001a252be5578", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa0f136dd2334583309d32b62544682ee972b51a", + "reference": "fa0f136dd2334583309d32b62544682ee972b51a", "shasum": "" }, "require": { @@ -4186,7 +4186,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", - "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.7" + "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.8" }, "funding": [ { @@ -4194,7 +4194,7 @@ "type": "github" } ], - "time": "2022-09-14T06:33:43+00:00" + "time": "2022-09-14T12:41:17+00:00" }, { "name": "sebastian/complexity", diff --git a/src/Appwrite/SMS/Adapter/Mock.php b/src/Appwrite/SMS/Adapter/Mock.php index 147f7ec9b..52f2fec57 100644 --- a/src/Appwrite/SMS/Adapter/Mock.php +++ b/src/Appwrite/SMS/Adapter/Mock.php @@ -4,12 +4,13 @@ namespace Appwrite\SMS\Adapter; use Appwrite\SMS\Adapter; +// Mock adapter used to E2E test worker class Mock extends Adapter { /** * @var string */ - public static string $digits = '123456'; + private string $endpoint = 'http://request-catcher:5000/mock-sms'; /** * @param string $from @@ -19,6 +20,19 @@ class Mock extends Adapter */ public function send(string $from, string $to, string $message): void { - return; + $this->request( + method: 'POST', + url: $this->endpoint, + payload: \json_encode([ + 'message' => $message, + 'from' => $from, + 'to' => $to + ]), + headers: [ + "content-type: application/json", + "x-username: {$this->user}", + "x-key: {$this->secret}", + ] + ); } } diff --git a/tests/e2e/Services/Account/AccountCustomClientTest.php b/tests/e2e/Services/Account/AccountCustomClientTest.php index 541e8e79f..0b7ef2d5b 100644 --- a/tests/e2e/Services/Account/AccountCustomClientTest.php +++ b/tests/e2e/Services/Account/AccountCustomClientTest.php @@ -716,7 +716,19 @@ class AccountCustomClientTest extends Scope $this->assertEquals(400, $response['headers']['status-code']); - $data['token'] = Mock::$digits; + \sleep(2); + + $smsRequest = $this->getLastRequest(); + + $this->assertEquals('http://request-catcher:5000/mock-sms', $smsRequest['url']); + $this->assertEquals('Appwrite Phone Authentication', $smsRequest['headers']['User-Agent']); + $this->assertEquals('username', $smsRequest['headers']['X-Username']); + $this->assertEquals('password', $smsRequest['headers']['X-Key']); + $this->assertEquals('POST', $smsRequest['method']); + $this->assertEquals('+123456789', $smsRequest['data']['from']); + $this->assertEquals($number, $smsRequest['data']['to']); + + $data['token'] = $smsRequest['data']['message']; $data['id'] = $userId; $data['number'] = $number; @@ -931,7 +943,13 @@ class AccountCustomClientTest extends Scope $this->assertEmpty($response['body']['secret']); $this->assertEquals(true, DateTime::isValid($response['body']['expire'])); - return $data; + \sleep(2); + + $smsRequest = $this->getLastRequest(); + + return \array_merge($data, [ + 'token' => $smsRequest['data']['message'] + ]); } /** @@ -941,6 +959,7 @@ class AccountCustomClientTest extends Scope { $id = $data['id'] ?? ''; $session = $data['session'] ?? ''; + $secret = $data['token'] ?? ''; /** * Test for SUCCESS @@ -952,7 +971,7 @@ class AccountCustomClientTest extends Scope 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session, ]), [ 'userId' => $id, - 'secret' => Mock::$digits, + 'secret' => $secret, ]); $this->assertEquals(200, $response['headers']['status-code']); @@ -967,7 +986,7 @@ class AccountCustomClientTest extends Scope 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session, ]), [ 'userId' => ID::custom('ewewe'), - 'secret' => Mock::$digits, + 'secret' => $secret, ]); $this->assertEquals(404, $response['headers']['status-code']); From 215c02ac472cc771e83c20865440b4f4ca08f06a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Mon, 19 Sep 2022 08:43:59 +0000 Subject: [PATCH 11/42] e2e tests for password re-hashing --- composer.lock | 12 +-- tests/e2e/Services/Users/UsersBase.php | 116 +++++++++---------------- 2 files changed, 45 insertions(+), 83 deletions(-) diff --git a/composer.lock b/composer.lock index 532e73ddb..3f8d4bc04 100644 --- a/composer.lock +++ b/composer.lock @@ -4124,16 +4124,16 @@ }, { "name": "sebastian/comparator", - "version": "4.0.7", + "version": "4.0.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "7fa545db548c90bdebeb9da0583001a252be5578" + "reference": "fa0f136dd2334583309d32b62544682ee972b51a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/7fa545db548c90bdebeb9da0583001a252be5578", - "reference": "7fa545db548c90bdebeb9da0583001a252be5578", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa0f136dd2334583309d32b62544682ee972b51a", + "reference": "fa0f136dd2334583309d32b62544682ee972b51a", "shasum": "" }, "require": { @@ -4186,7 +4186,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", - "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.7" + "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.8" }, "funding": [ { @@ -4194,7 +4194,7 @@ "type": "github" } ], - "time": "2022-09-14T06:33:43+00:00" + "time": "2022-09-14T12:41:17+00:00" }, { "name": "sebastian/complexity", diff --git a/tests/e2e/Services/Users/UsersBase.php b/tests/e2e/Services/Users/UsersBase.php index 845510a6b..627f12d3d 100644 --- a/tests/e2e/Services/Users/UsersBase.php +++ b/tests/e2e/Services/Users/UsersBase.php @@ -180,89 +180,51 @@ trait UsersBase */ public function testCreateUserSessionHashed(array $data): void { - $response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ]), [ - 'email' => 'md5@appwrite.io', - 'password' => 'appwrite', - ]); + $userIds = [ 'md5', 'bcrypt', 'argon2', 'sha512', 'scrypt', 'phpass', 'scrypt-modified' ]; - $this->assertEquals($response['headers']['status-code'], 201); - $this->assertEquals($response['body']['userId'], 'md5'); + foreach ($userIds as $userId) { + // Ensure sessions can be created with hashed passwords + $response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ]), [ + 'email' => $userId . '@appwrite.io', + 'password' => 'appwrite', + ]); - $response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ]), [ - 'email' => 'bcrypt@appwrite.io', - 'password' => 'appwrite', - ]); + $this->assertEquals(201, $response['headers']['status-code']); + $this->assertEquals($userId, $response['body']['userId']); + } - $this->assertEquals($response['headers']['status-code'], 201); - $this->assertEquals($response['body']['userId'], 'bcrypt'); + foreach ($userIds as $userId) { + // Ensure all passwords were re-hashed + $response = $this->client->call(Client::METHOD_GET, '/users/' . $userId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), []); - $response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ]), [ - 'email' => 'argon2@appwrite.io', - 'password' => 'appwrite', - ]); + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals($userId, $response['body']['$id']); + $this->assertEquals($userId . '@appwrite.io', $response['body']['email']); + $this->assertEquals('argon2', $response['body']['hash']); + $this->assertStringStartsWith('$argon2', $response['body']['password']); + } - $this->assertEquals($response['headers']['status-code'], 201); - $this->assertEquals($response['body']['userId'], 'argon2'); + foreach ($userIds as $userId) { + // Ensure sessions can be created after re-hashing of passwords + $response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ]), [ + 'email' => $userId . '@appwrite.io', + 'password' => 'appwrite', + ]); - $response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ]), [ - 'email' => 'sha512@appwrite.io', - 'password' => 'appwrite', - ]); - - $this->assertEquals($response['headers']['status-code'], 201); - $this->assertEquals($response['body']['userId'], 'sha512'); - - $response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ]), [ - 'email' => 'scrypt@appwrite.io', - 'password' => 'appwrite', - ]); - - $this->assertEquals($response['headers']['status-code'], 201); - $this->assertEquals($response['body']['userId'], 'scrypt'); - - $response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ]), [ - 'email' => 'phpass@appwrite.io', - 'password' => 'appwrite', - ]); - - $this->assertEquals($response['headers']['status-code'], 201); - $this->assertEquals($response['body']['userId'], 'phpass'); - - $response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ]), [ - 'email' => 'scrypt-modified@appwrite.io', - 'password' => 'appwrite', - ]); - - $this->assertEquals($response['headers']['status-code'], 201); - $this->assertEquals($response['body']['userId'], 'scrypt-modified'); + $this->assertEquals(201, $response['headers']['status-code']); + $this->assertEquals($userId, $response['body']['userId']); + } } /** From a983e792d8260f888ed125eb7543ee124e5603eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Mon, 19 Sep 2022 10:05:42 +0000 Subject: [PATCH 12/42] Refactor required param values --- app/controllers/api/account.php | 6 +-- app/controllers/api/databases.php | 18 ++++----- app/controllers/api/functions.php | 16 ++++---- app/controllers/api/projects.php | 62 +++++++++++++++---------------- app/controllers/api/storage.php | 16 ++++---- app/controllers/api/teams.php | 2 +- app/controllers/api/users.php | 2 +- 7 files changed, 61 insertions(+), 61 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index ef94b251c..0cd1c59b7 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -1418,7 +1418,7 @@ App::get('/v1/account/sessions/:sessionId') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_SESSION) - ->param('sessionId', null, new UID(), 'Session ID. Use the string \'current\' to get the current device session.') + ->param('sessionId', '', new UID(), 'Session ID. Use the string \'current\' to get the current device session.') ->inject('response') ->inject('user') ->inject('locale') @@ -1696,7 +1696,7 @@ App::delete('/v1/account/sessions/:sessionId') ->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT) ->label('sdk.response.model', Response::MODEL_NONE) ->label('abuse-limit', 100) - ->param('sessionId', null, new UID(), 'Session ID. Use the string \'current\' to delete the current device session.') + ->param('sessionId', '', new UID(), 'Session ID. Use the string \'current\' to delete the current device session.') ->inject('request') ->inject('response') ->inject('user') @@ -1769,7 +1769,7 @@ App::patch('/v1/account/sessions/:sessionId') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_SESSION) ->label('abuse-limit', 10) - ->param('sessionId', null, new UID(), 'Session ID. Use the string \'current\' to update the current device session.') + ->param('sessionId', '', new UID(), 'Session ID. Use the string \'current\' to update the current device session.') ->inject('request') ->inject('response') ->inject('user') diff --git a/app/controllers/api/databases.php b/app/controllers/api/databases.php index e2acb3077..56521693f 100644 --- a/app/controllers/api/databases.php +++ b/app/controllers/api/databases.php @@ -1783,7 +1783,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/indexes/:key') ->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT) ->label('sdk.response.model', Response::MODEL_NONE) ->param('databaseId', '', new UID(), 'Database ID.') - ->param('collectionId', null, new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).') + ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).') ->param('key', '', new Key(), 'Index Key.') ->inject('response') ->inject('dbForProject') @@ -1856,7 +1856,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents') ->label('sdk.response.model', Response::MODEL_DOCUMENT) ->param('databaseId', '', new UID(), 'Database ID.') ->param('documentId', '', new CustomId(), 'Document ID. Choose your own unique ID or pass the string "unique()" to auto generate it. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.') - ->param('collectionId', null, new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection). Make sure to define attributes before creating documents.') + ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection). Make sure to define attributes before creating documents.') ->param('data', [], new JSON(), 'Document data as JSON object.') ->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE]), 'An array of permissions strings. By default the current user is granted with all permissions. [Learn more about permissions](/docs/permissions).', true) ->inject('response') @@ -2074,8 +2074,8 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_DOCUMENT) ->param('databaseId', '', new UID(), 'Database ID.') - ->param('collectionId', null, new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).') - ->param('documentId', null, new UID(), 'Document ID.') + ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).') + ->param('documentId', '', new UID(), 'Document ID.') ->inject('response') ->inject('dbForProject') ->inject('mode') @@ -2137,7 +2137,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen ->label('sdk.response.model', Response::MODEL_LOG_LIST) ->param('databaseId', '', new UID(), 'Database ID.') ->param('collectionId', '', new UID(), 'Collection ID.') - ->param('documentId', null, new UID(), 'Document ID.') + ->param('documentId', '', new UID(), 'Document ID.') ->param('queries', [], new Queries(new Limit(), new Offset()), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Only supported methods are limit and offset', true) ->inject('response') ->inject('dbForProject') @@ -2243,8 +2243,8 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_DOCUMENT) ->param('databaseId', '', new UID(), 'Database ID.') - ->param('collectionId', null, new UID(), 'Collection ID.') - ->param('documentId', null, new UID(), 'Document ID.') + ->param('collectionId', '', new UID(), 'Collection ID.') + ->param('documentId', '', new UID(), 'Document ID.') ->param('data', [], new JSON(), 'Document data as JSON object. Include only attribute and value pairs to be updated.', true) ->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE]), 'An array of permissions strings. By default the current permissions are inherited. [Learn more about permissions](/docs/permissions).', true) ->inject('response') @@ -2376,8 +2376,8 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu ->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT) ->label('sdk.response.model', Response::MODEL_NONE) ->param('databaseId', '', new UID(), 'Database ID.') - ->param('collectionId', null, new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).') - ->param('documentId', null, new UID(), 'Document ID.') + ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).') + ->param('documentId', '', new UID(), 'Document ID.') ->inject('response') ->inject('dbForProject') ->inject('events') diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index f42d1059e..de14f81f3 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -1332,7 +1332,7 @@ App::post('/v1/functions/:functionId/variables') ->label('sdk.response.code', Response::STATUS_CODE_CREATED) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_VARIABLE) - ->param('functionId', null, new UID(), 'Function unique ID.', false) + ->param('functionId', '', new UID(), 'Function unique ID.', false) ->param('key', null, new Text(Database::LENGTH_KEY), 'Variable key. Max length: ' . Database::LENGTH_KEY . ' chars.', false) ->param('value', null, new Text(8192), 'Variable value. Max length: 8192 chars.', false) ->inject('response') @@ -1384,7 +1384,7 @@ App::get('/v1/functions/:functionId/variables') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_VARIABLE_LIST) - ->param('functionId', null, new UID(), 'Function unique ID.', false) + ->param('functionId', '', new UID(), 'Function unique ID.', false) ->inject('response') ->inject('dbForProject') ->action(function (string $functionId, Response $response, Database $dbForProject) { @@ -1411,8 +1411,8 @@ App::get('/v1/functions/:functionId/variables/:variableId') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_VARIABLE) - ->param('functionId', null, new UID(), 'Function unique ID.', false) - ->param('variableId', null, new UID(), 'Variable unique ID.', false) + ->param('functionId', '', new UID(), 'Function unique ID.', false) + ->param('variableId', '', new UID(), 'Variable unique ID.', false) ->inject('response') ->inject('dbForProject') ->action(function (string $functionId, string $variableId, Response $response, Database $dbForProject) { @@ -1447,8 +1447,8 @@ App::put('/v1/functions/:functionId/variables/:variableId') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_VARIABLE) - ->param('functionId', null, new UID(), 'Function unique ID.', false) - ->param('variableId', null, new UID(), 'Variable unique ID.', false) + ->param('functionId', '', new UID(), 'Function unique ID.', false) + ->param('variableId', '', new UID(), 'Variable unique ID.', false) ->param('key', null, new Text(255), 'Variable key. Max length: 255 chars.', false) ->param('value', null, new Text(8192), 'Variable value. Max length: 8192 chars.', true) ->inject('response') @@ -1499,8 +1499,8 @@ App::delete('/v1/functions/:functionId/variables/:variableId') ->label('sdk.description', '/docs/references/functions/delete-variable.md') ->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT) ->label('sdk.response.model', Response::MODEL_NONE) - ->param('functionId', null, new UID(), 'Function unique ID.', false) - ->param('variableId', null, new UID(), 'Variable unique ID.', false) + ->param('functionId', '', new UID(), 'Function unique ID.', false) + ->param('variableId', '', new UID(), 'Variable unique ID.', false) ->inject('response') ->inject('dbForProject') ->action(function (string $functionId, string $variableId, Response $response, Database $dbForProject) { diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index 5c8ba8a26..0ba9384b1 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -584,7 +584,7 @@ App::post('/v1/projects/:projectId/webhooks') ->label('sdk.response.code', Response::STATUS_CODE_CREATED) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_WEBHOOK) - ->param('projectId', null, new UID(), 'Project unique ID.') + ->param('projectId', '', new UID(), 'Project unique ID.') ->param('name', null, new Text(128), 'Webhook name. Max length: 128 chars.') ->param('events', null, new ArrayList(new Event(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Events list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' events are allowed.') ->param('url', null, new URL(['http', 'https']), 'Webhook URL.') @@ -672,8 +672,8 @@ App::get('/v1/projects/:projectId/webhooks/:webhookId') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_WEBHOOK) - ->param('projectId', null, new UID(), 'Project unique ID.') - ->param('webhookId', null, new UID(), 'Webhook unique ID.') + ->param('projectId', '', new UID(), 'Project unique ID.') + ->param('webhookId', '', new UID(), 'Webhook unique ID.') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, string $webhookId, Response $response, Database $dbForConsole) { @@ -706,8 +706,8 @@ App::put('/v1/projects/:projectId/webhooks/:webhookId') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_WEBHOOK) - ->param('projectId', null, new UID(), 'Project unique ID.') - ->param('webhookId', null, new UID(), 'Webhook unique ID.') + ->param('projectId', '', new UID(), 'Project unique ID.') + ->param('webhookId', '', new UID(), 'Webhook unique ID.') ->param('name', null, new Text(128), 'Webhook name. Max length: 128 chars.') ->param('events', null, new ArrayList(new Event(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Events list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' events are allowed.') ->param('url', null, new URL(['http', 'https']), 'Webhook URL.') @@ -760,8 +760,8 @@ App::patch('/v1/projects/:projectId/webhooks/:webhookId/signature') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_WEBHOOK) - ->param('projectId', null, new UID(), 'Project unique ID.') - ->param('webhookId', null, new UID(), 'Webhook unique ID.') + ->param('projectId', '', new UID(), 'Project unique ID.') + ->param('webhookId', '', new UID(), 'Webhook unique ID.') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, string $webhookId, Response $response, Database $dbForConsole) { @@ -798,8 +798,8 @@ App::delete('/v1/projects/:projectId/webhooks/:webhookId') ->label('sdk.method', 'deleteWebhook') ->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT) ->label('sdk.response.model', Response::MODEL_NONE) - ->param('projectId', null, new UID(), 'Project unique ID.') - ->param('webhookId', null, new UID(), 'Webhook unique ID.') + ->param('projectId', '', new UID(), 'Project unique ID.') + ->param('webhookId', '', new UID(), 'Webhook unique ID.') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, string $webhookId, Response $response, Database $dbForConsole) { @@ -838,7 +838,7 @@ App::post('/v1/projects/:projectId/keys') ->label('sdk.response.code', Response::STATUS_CODE_CREATED) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_KEY) - ->param('projectId', null, new UID(), 'Project unique ID.') + ->param('projectId', '', new UID(), 'Project unique ID.') ->param('name', null, new Text(128), 'Key name. Max length: 128 chars.') ->param('scopes', null, new ArrayList(new WhiteList(array_keys(Config::getParam('scopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Key scopes list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed.') ->param('expire', null, new DatetimeValidator(), 'Expiration time in ISO 8601 format. Use null for unlimited expiration.', true) @@ -888,7 +888,7 @@ App::get('/v1/projects/:projectId/keys') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_KEY_LIST) - ->param('projectId', null, new UID(), 'Project unique ID.') + ->param('projectId', '', new UID(), 'Project unique ID.') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, Response $response, Database $dbForConsole) { @@ -920,8 +920,8 @@ App::get('/v1/projects/:projectId/keys/:keyId') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_KEY) - ->param('projectId', null, new UID(), 'Project unique ID.') - ->param('keyId', null, new UID(), 'Key unique ID.') + ->param('projectId', '', new UID(), 'Project unique ID.') + ->param('keyId', '', new UID(), 'Key unique ID.') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, string $keyId, Response $response, Database $dbForConsole) { @@ -954,8 +954,8 @@ App::put('/v1/projects/:projectId/keys/:keyId') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_KEY) - ->param('projectId', null, new UID(), 'Project unique ID.') - ->param('keyId', null, new UID(), 'Key unique ID.') + ->param('projectId', '', new UID(), 'Project unique ID.') + ->param('keyId', '', new UID(), 'Key unique ID.') ->param('name', null, new Text(128), 'Key name. Max length: 128 chars.') ->param('scopes', null, new ArrayList(new WhiteList(array_keys(Config::getParam('scopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Key scopes list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' events are allowed.') ->param('expire', null, new DatetimeValidator(), 'Expiration time in ISO 8601 format. Use null for unlimited expiration.', true) @@ -1000,8 +1000,8 @@ App::delete('/v1/projects/:projectId/keys/:keyId') ->label('sdk.method', 'deleteKey') ->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT) ->label('sdk.response.model', Response::MODEL_NONE) - ->param('projectId', null, new UID(), 'Project unique ID.') - ->param('keyId', null, new UID(), 'Key unique ID.') + ->param('projectId', '', new UID(), 'Project unique ID.') + ->param('keyId', '', new UID(), 'Key unique ID.') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, string $keyId, Response $response, Database $dbForConsole) { @@ -1040,7 +1040,7 @@ App::post('/v1/projects/:projectId/platforms') ->label('sdk.response.code', Response::STATUS_CODE_CREATED) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_PLATFORM) - ->param('projectId', null, new UID(), 'Project unique ID.') + ->param('projectId', '', new UID(), 'Project unique ID.') ->param('type', null, new WhiteList([Origin::CLIENT_TYPE_WEB, Origin::CLIENT_TYPE_FLUTTER_IOS, Origin::CLIENT_TYPE_FLUTTER_ANDROID, Origin::CLIENT_TYPE_FLUTTER_LINUX, Origin::CLIENT_TYPE_FLUTTER_MACOS, Origin::CLIENT_TYPE_FLUTTER_WINDOWS, Origin::CLIENT_TYPE_APPLE_IOS, Origin::CLIENT_TYPE_APPLE_MACOS, Origin::CLIENT_TYPE_APPLE_WATCHOS, Origin::CLIENT_TYPE_APPLE_TVOS, Origin::CLIENT_TYPE_ANDROID, Origin::CLIENT_TYPE_UNITY], true), 'Platform type.') ->param('name', null, new Text(128), 'Platform name. Max length: 128 chars.') ->param('key', '', new Text(256), 'Package name for Android or bundle ID for iOS or macOS. Max length: 256 chars.', true) @@ -1122,8 +1122,8 @@ App::get('/v1/projects/:projectId/platforms/:platformId') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_PLATFORM) - ->param('projectId', null, new UID(), 'Project unique ID.') - ->param('platformId', null, new UID(), 'Platform unique ID.') + ->param('projectId', '', new UID(), 'Project unique ID.') + ->param('platformId', '', new UID(), 'Platform unique ID.') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, string $platformId, Response $response, Database $dbForConsole) { @@ -1156,8 +1156,8 @@ App::put('/v1/projects/:projectId/platforms/:platformId') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_PLATFORM) - ->param('projectId', null, new UID(), 'Project unique ID.') - ->param('platformId', null, new UID(), 'Platform unique ID.') + ->param('projectId', '', new UID(), 'Project unique ID.') + ->param('platformId', '', new UID(), 'Platform unique ID.') ->param('name', null, new Text(128), 'Platform name. Max length: 128 chars.') ->param('key', '', new Text(256), 'Package name for android or bundle ID for iOS. Max length: 256 chars.', true) ->param('store', '', new Text(256), 'App store or Google Play store ID. Max length: 256 chars.', true) @@ -1203,8 +1203,8 @@ App::delete('/v1/projects/:projectId/platforms/:platformId') ->label('sdk.method', 'deletePlatform') ->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT) ->label('sdk.response.model', Response::MODEL_NONE) - ->param('projectId', null, new UID(), 'Project unique ID.') - ->param('platformId', null, new UID(), 'Platform unique ID.') + ->param('projectId', '', new UID(), 'Project unique ID.') + ->param('platformId', '', new UID(), 'Platform unique ID.') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, string $platformId, Response $response, Database $dbForConsole) { @@ -1243,7 +1243,7 @@ App::post('/v1/projects/:projectId/domains') ->label('sdk.response.code', Response::STATUS_CODE_CREATED) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_DOMAIN) - ->param('projectId', null, new UID(), 'Project unique ID.') + ->param('projectId', '', new UID(), 'Project unique ID.') ->param('domain', null, new DomainValidator(), 'Domain name.') ->inject('response') ->inject('dbForConsole') @@ -1340,8 +1340,8 @@ App::get('/v1/projects/:projectId/domains/:domainId') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_DOMAIN) - ->param('projectId', null, new UID(), 'Project unique ID.') - ->param('domainId', null, new UID(), 'Domain unique ID.') + ->param('projectId', '', new UID(), 'Project unique ID.') + ->param('domainId', '', new UID(), 'Domain unique ID.') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, string $domainId, Response $response, Database $dbForConsole) { @@ -1374,8 +1374,8 @@ App::patch('/v1/projects/:projectId/domains/:domainId/verification') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_DOMAIN) - ->param('projectId', null, new UID(), 'Project unique ID.') - ->param('domainId', null, new UID(), 'Domain unique ID.') + ->param('projectId', '', new UID(), 'Project unique ID.') + ->param('domainId', '', new UID(), 'Domain unique ID.') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, string $domainId, Response $response, Database $dbForConsole) { @@ -1433,8 +1433,8 @@ App::delete('/v1/projects/:projectId/domains/:domainId') ->label('sdk.method', 'deleteDomain') ->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT) ->label('sdk.response.model', Response::MODEL_NONE) - ->param('projectId', null, new UID(), 'Project unique ID.') - ->param('domainId', null, new UID(), 'Domain unique ID.') + ->param('projectId', '', new UID(), 'Project unique ID.') + ->param('domainId', '', new UID(), 'Domain unique ID.') ->inject('response') ->inject('dbForConsole') ->inject('deletes') diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index 1134887fb..e9f9fe3e4 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -346,7 +346,7 @@ App::post('/v1/storage/buckets/:bucketId/files') ->label('sdk.response.code', Response::STATUS_CODE_CREATED) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_FILE) - ->param('bucketId', null, new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).') + ->param('bucketId', '', new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).') ->param('fileId', '', new CustomId(), 'File ID. Choose your own unique ID or pass the string "unique()" to auto generate it. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.') ->param('file', [], new File(), 'Binary file.', false) ->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE]), 'An array of permission strings. By default the current user is granted with all permissions. [Learn more about permissions](/docs/permissions).', true) @@ -667,7 +667,7 @@ App::get('/v1/storage/buckets/:bucketId/files') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_FILE_LIST) - ->param('bucketId', null, new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).') + ->param('bucketId', '', new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).') ->param('queries', [], new Files(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Files::ALLOWED_ATTRIBUTES), true) ->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true) ->inject('response') @@ -744,7 +744,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_FILE) - ->param('bucketId', null, new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).') + ->param('bucketId', '', new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).') ->param('fileId', '', new UID(), 'File ID.') ->inject('response') ->inject('dbForProject') @@ -793,7 +793,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_IMAGE) ->label('sdk.methodType', 'location') - ->param('bucketId', null, new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).') + ->param('bucketId', '', new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).') ->param('fileId', '', new UID(), 'File ID') ->param('width', 0, new Range(0, 4000), 'Resize preview image width, Pass an integer between 0 to 4000.', true) ->param('height', 0, new Range(0, 4000), 'Resize preview image height, Pass an integer between 0 to 4000.', true) @@ -959,7 +959,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', '*/*') ->label('sdk.methodType', 'location') - ->param('bucketId', null, new UID(), 'Storage bucket ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).') + ->param('bucketId', '', new UID(), 'Storage bucket ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).') ->param('fileId', '', new UID(), 'File ID.') ->inject('request') ->inject('response') @@ -1099,7 +1099,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', '*/*') ->label('sdk.methodType', 'location') - ->param('bucketId', null, new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).') + ->param('bucketId', '', new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).') ->param('fileId', '', new UID(), 'File ID.') ->inject('response') ->inject('request') @@ -1256,7 +1256,7 @@ App::put('/v1/storage/buckets/:bucketId/files/:fileId') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_FILE) - ->param('bucketId', null, new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).') + ->param('bucketId', '', new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).') ->param('fileId', '', new UID(), 'File unique ID.') ->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE]), 'An array of permission string. By default the current permissions are inherited. [Learn more about permissions](/docs/permissions).', true) ->inject('response') @@ -1358,7 +1358,7 @@ App::delete('/v1/storage/buckets/:bucketId/files/:fileId') ->label('sdk.description', '/docs/references/storage/delete-file.md') ->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT) ->label('sdk.response.model', Response::MODEL_NONE) - ->param('bucketId', null, new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).') + ->param('bucketId', '', new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).') ->param('fileId', '', new UID(), 'File ID.') ->inject('response') ->inject('dbForProject') diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index c97537022..fae33da1d 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -865,7 +865,7 @@ App::get('/v1/teams/:teamId/logs') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_LOG_LIST) - ->param('teamId', null, new UID(), 'Team ID.') + ->param('teamId', '', new UID(), 'Team ID.') ->param('queries', [], new Queries(new Limit(), new Offset()), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Only supported methods are limit and offset', true) ->inject('response') ->inject('dbForProject') diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index c83f03d96..6986d7811 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -981,7 +981,7 @@ App::delete('/v1/users/:userId/sessions/:sessionId') ->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT) ->label('sdk.response.model', Response::MODEL_NONE) ->param('userId', '', new UID(), 'User ID.') - ->param('sessionId', null, new UID(), 'Session ID.') + ->param('sessionId', '', new UID(), 'Session ID.') ->inject('response') ->inject('dbForProject') ->inject('events') From c42bf2d8dabb817d3b76d82062ab3a8c6691b2c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Mon, 19 Sep 2022 11:43:21 +0000 Subject: [PATCH 13/42] Fix documentation response model --- app/controllers/api/teams.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index c97537022..801c647a2 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -553,7 +553,7 @@ App::get('/v1/teams/:teamId/memberships/:membershipId') ->label('sdk.description', '/docs/references/teams/get-team-member.md') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) - ->label('sdk.response.model', Response::MODEL_MEMBERSHIP_LIST) + ->label('sdk.response.model', Response::MODEL_MEMBERSHIP) ->param('teamId', '', new UID(), 'Team ID.') ->param('membershipId', '', new UID(), 'Membership ID.') ->inject('response') From e31c7f7740fb6d0f07ec42dc5bf90b0dcc9b108b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Mon, 19 Sep 2022 11:53:34 +0000 Subject: [PATCH 14/42] Improve delete worker for functions --- app/workers/deletes.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/workers/deletes.php b/app/workers/deletes.php index 2ec819940..19ab56c69 100644 --- a/app/workers/deletes.php +++ b/app/workers/deletes.php @@ -410,6 +410,14 @@ class DeletesV1 extends Worker $dbForProject = $this->getProjectDB($projectId); $functionId = $document->getId(); + /** + * Delete Variables + */ + Console::info("Deleting variables for function " . $functionId); + $this->deleteByGroup('variables', [ + Query::equal('functionId', [$functionId]) + ], $dbForProject); + /** * Delete Deployments */ From 89afad1a32ae304da2fa92f2823beb4bcd3cc9bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Mon, 19 Sep 2022 11:58:41 +0000 Subject: [PATCH 15/42] Replace nulls with empty strings --- app/controllers/api/functions.php | 18 +++++++++--------- app/workers/functions.php | 22 +++++++++++----------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index f42d1059e..9c6e92d41 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -1066,21 +1066,21 @@ App::post('/v1/functions/:functionId/executions') } $vars = array_reduce($function['vars'] ?? [], function (array $carry, Document $var) { - $carry[$var->getAttribute('key')] = $var->getAttribute('value'); + $carry[$var->getAttribute('key')] = $var->getAttribute('value') ?? ''; return $carry; }, []); $vars = \array_merge($vars, [ - 'APPWRITE_FUNCTION_ID' => $function->getId(), + 'APPWRITE_FUNCTION_ID' => $function->getId() ?? '', 'APPWRITE_FUNCTION_NAME' => $function->getAttribute('name', ''), - 'APPWRITE_FUNCTION_DEPLOYMENT' => $deployment->getId(), + 'APPWRITE_FUNCTION_DEPLOYMENT' => $deployment->getId() ?? '', 'APPWRITE_FUNCTION_TRIGGER' => 'http', - 'APPWRITE_FUNCTION_RUNTIME_NAME' => $runtime['name'], - 'APPWRITE_FUNCTION_RUNTIME_VERSION' => $runtime['version'], - 'APPWRITE_FUNCTION_DATA' => $data, - 'APPWRITE_FUNCTION_PROJECT_ID' => $project->getId(), - 'APPWRITE_FUNCTION_USER_ID' => $user->getId(), - 'APPWRITE_FUNCTION_JWT' => $jwt, + 'APPWRITE_FUNCTION_RUNTIME_NAME' => $runtime['name'] ?? '', + 'APPWRITE_FUNCTION_RUNTIME_VERSION' => $runtime['version'] ?? '', + 'APPWRITE_FUNCTION_DATA' => $data ?? '', + 'APPWRITE_FUNCTION_PROJECT_ID' => $project->getId() ?? '', + 'APPWRITE_FUNCTION_USER_ID' => $user->getId() ?? '', + 'APPWRITE_FUNCTION_JWT' => $jwt ?? '', ]); /** Execute function */ diff --git a/app/workers/functions.php b/app/workers/functions.php index 31b74a11c..e74c7a15b 100644 --- a/app/workers/functions.php +++ b/app/workers/functions.php @@ -270,18 +270,18 @@ class FunctionsV1 extends Worker /** Collect environment variables */ $vars = \array_merge($vars, [ - 'APPWRITE_FUNCTION_ID' => $functionId, + 'APPWRITE_FUNCTION_ID' => $functionId ?? '', 'APPWRITE_FUNCTION_NAME' => $function->getAttribute('name', ''), - 'APPWRITE_FUNCTION_DEPLOYMENT' => $deploymentId, - 'APPWRITE_FUNCTION_RUNTIME_NAME' => $runtime['name'], - 'APPWRITE_FUNCTION_RUNTIME_VERSION' => $runtime['version'], - 'APPWRITE_FUNCTION_TRIGGER' => $trigger, - 'APPWRITE_FUNCTION_EVENT' => $event, - 'APPWRITE_FUNCTION_EVENT_DATA' => $eventData, - 'APPWRITE_FUNCTION_DATA' => $data, - 'APPWRITE_FUNCTION_PROJECT_ID' => $project->getId(), - 'APPWRITE_FUNCTION_USER_ID' => $user->getId(), - 'APPWRITE_FUNCTION_JWT' => $jwt, + 'APPWRITE_FUNCTION_DEPLOYMENT' => $deploymentId ?? '', + 'APPWRITE_FUNCTION_RUNTIME_NAME' => $runtime['name'] ?? '', + 'APPWRITE_FUNCTION_RUNTIME_VERSION' => $runtime['version'] ?? '', + 'APPWRITE_FUNCTION_TRIGGER' => $trigger ?? '', + 'APPWRITE_FUNCTION_EVENT' => $event ?? '', + 'APPWRITE_FUNCTION_EVENT_DATA' => $eventData ?? '', + 'APPWRITE_FUNCTION_DATA' => $data ?? '', + 'APPWRITE_FUNCTION_PROJECT_ID' => $project->getId() ?? '', + 'APPWRITE_FUNCTION_USER_ID' => $user->getId() ?? '', + 'APPWRITE_FUNCTION_JWT' => $jwt ?? '', ]); /** Execute function */ From 01f77fa9932ab1ae14b53889d0dd551ad2155d52 Mon Sep 17 00:00:00 2001 From: Steven Nguyen Date: Thu, 15 Sep 2022 16:16:30 +0000 Subject: [PATCH 16/42] Ensure empty datetimes are 0 in 0.15 response Because empty datetimes are invalid, strtotime returned false so the response was false rather than 0. --- src/Appwrite/Utopia/Response/Filters/V15.php | 6 +++++- tests/unit/Utopia/Response/Filters/V15Test.php | 10 +++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/Appwrite/Utopia/Response/Filters/V15.php b/src/Appwrite/Utopia/Response/Filters/V15.php index 8e688d58f..a859c6c52 100644 --- a/src/Appwrite/Utopia/Response/Filters/V15.php +++ b/src/Appwrite/Utopia/Response/Filters/V15.php @@ -210,7 +210,11 @@ class V15 extends Filter protected function parseDatetimeAttributes(array $content, array $attributes): array { foreach ($attributes as $attribute) { - if (isset($content[$attribute])) { + if (array_key_exists($attribute, $content)) { + if (empty($content[$attribute])) { + $content[$attribute] = 0; + continue; + } $content[$attribute] = strtotime($content[$attribute]); } } diff --git a/tests/unit/Utopia/Response/Filters/V15Test.php b/tests/unit/Utopia/Response/Filters/V15Test.php index a68da474d..76b8b20b5 100644 --- a/tests/unit/Utopia/Response/Filters/V15Test.php +++ b/tests/unit/Utopia/Response/Filters/V15Test.php @@ -1077,6 +1077,14 @@ class V15Test extends TestCase 'providerAccessTokenExpiry' => 1592981250, ], ], + 'empty values' => [ + [ + 'providerAccessTokenExpiry' => '', + ], + [ + 'providerAccessTokenExpiry' => 0, + ], + ], ]; } @@ -1089,7 +1097,7 @@ class V15Test extends TestCase $result = $this->filter->parse($content, $model); - $this->assertEquals($expected, $result); + $this->assertSame($expected, $result); } /** From 2f08ec0926dc3793d1dea8c002b14595008a2a87 Mon Sep 17 00:00:00 2001 From: Steven Nguyen Date: Thu, 15 Sep 2022 18:43:53 +0000 Subject: [PATCH 17/42] Convert Document $collectionId in 0.15 response Document $collection was renamed to $collectionId to match the naming convention of referring to other resource IDs. This ensures backwards compatibility. --- src/Appwrite/Utopia/Response/Filters/V15.php | 28 +++++++++++++------ .../unit/Utopia/Response/Filters/V15Test.php | 25 +++++++++++++++++ 2 files changed, 44 insertions(+), 9 deletions(-) diff --git a/src/Appwrite/Utopia/Response/Filters/V15.php b/src/Appwrite/Utopia/Response/Filters/V15.php index a859c6c52..a74f0f32a 100644 --- a/src/Appwrite/Utopia/Response/Filters/V15.php +++ b/src/Appwrite/Utopia/Response/Filters/V15.php @@ -91,20 +91,17 @@ class V15 extends Filter $parsedResponse[$listKey] = array_map(fn ($content) => $this->parseCreatedAtUpdatedAt($content), $parsedResponse[$listKey]); break; case Response::MODEL_DOCUMENT: + $parsedResponse = $this->parseDocument($parsedResponse); + break; case Response::MODEL_FILE: $parsedResponse = $this->parsePermissionsCreatedAtUpdatedAt($parsedResponse); break; case Response::MODEL_DOCUMENT_LIST: + $listKey = 'documents'; + $parsedResponse[$listKey] = array_map(fn ($content) => $this->parseDocument($content), $parsedResponse[$listKey]); + break; case Response::MODEL_FILE_LIST: - $listKey = ''; - switch ($model) { - case Response::MODEL_DOCUMENT_LIST: - $listKey = 'documents'; - break; - case Response::MODEL_FILE_LIST: - $listKey = 'files'; - break; - } + $listKey = 'files'; $parsedResponse[$listKey] = array_map(fn ($content) => $this->parsePermissionsCreatedAtUpdatedAt($content), $parsedResponse[$listKey]); break; case Response::MODEL_EXECUTION: @@ -318,6 +315,19 @@ class V15 extends Filter return $content; } + protected function parseDocument(array $content) + { + if (isset($content['$collectionId'])) { + $content['$collection'] = $content['$collectionId']; + unset($content['$collectionId']); + } + + unset($content['$databaseId']); + + $content = $this->parsePermissionsCreatedAtUpdatedAt($content); + return $content; + } + private function parseExecution($content) { unset($content['stdout']); diff --git a/tests/unit/Utopia/Response/Filters/V15Test.php b/tests/unit/Utopia/Response/Filters/V15Test.php index 76b8b20b5..ce7870483 100644 --- a/tests/unit/Utopia/Response/Filters/V15Test.php +++ b/tests/unit/Utopia/Response/Filters/V15Test.php @@ -502,9 +502,34 @@ class V15Test extends TestCase $this->assertEquals($expected, $result); } + public function documentProvider(): array + { + return [ + 'basic document' => [ + [ + '$id' => '5e5ea5c16897e', + '$collectionId' => '5e5ea5c15117e', + '$databaseId' => '5e5ea5c15117e', + '$createdAt' => '2020-06-24T06:47:30.000Z', + '$updatedAt' => '2020-06-24T06:47:30.000Z', + '$permissions' => [Permission::read(Role::any())] + ], + [ + '$id' => '5e5ea5c16897e', + '$collection' => '5e5ea5c15117e', + '$createdAt' => 1592981250, + '$updatedAt' => 1592981250, + '$read' => ['role:all'], + '$write' => [], + ], + ], + ]; + } + /** * @dataProvider createdAtUpdatedAtProvider * @dataProvider permissionsProvider + * @dataProvider documentProvider */ public function testDocument(array $content, array $expected): void { From 96134f2a5e1cda25a0156fb5325f3e26db11ef57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Tue, 20 Sep 2022 08:29:34 +0000 Subject: [PATCH 18/42] Remove todo --- app/workers/builds.php | 1 - 1 file changed, 1 deletion(-) diff --git a/app/workers/builds.php b/app/workers/builds.php index b2fac6710..bf780c646 100644 --- a/app/workers/builds.php +++ b/app/workers/builds.php @@ -110,7 +110,6 @@ class BuildsV1 extends Worker /** Trigger Webhook */ $deploymentModel = new Deployment(); - // TODO: Use Webhook() event here. If doesnt exist, create class for it $deploymentUpdate = new Event(Event::WEBHOOK_QUEUE_NAME, Event::WEBHOOK_CLASS_NAME); $deploymentUpdate ->setProject($project) From ef4ffcf3a216496a7eb100a2b396e25b3c5b9353 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 20 Sep 2022 22:03:13 +1200 Subject: [PATCH 19/42] Clear existing permissions for a role on keydown --- app/views/console/comps/permissions-matrix.phtml | 1 + public/dist/scripts/app-all.js | 2 +- public/dist/scripts/app.js | 2 +- public/scripts/permissions-matrix.js | 8 ++++++++ 4 files changed, 11 insertions(+), 2 deletions(-) diff --git a/app/views/console/comps/permissions-matrix.phtml b/app/views/console/comps/permissions-matrix.phtml index 836b0843a..80a424fc4 100644 --- a/app/views/console/comps/permissions-matrix.phtml +++ b/app/views/console/comps/permissions-matrix.phtml @@ -81,6 +81,7 @@ $escapedPermissions = \array_map(function ($perm) { list="types" type="text" x-model="permission.role" + @keydown="clearPermission(index)" @keyup="updatePermission(index)"/> diff --git a/public/dist/scripts/app-all.js b/public/dist/scripts/app-all.js index 11b2bcdfd..4ebaa92ba 100644 --- a/public/dist/scripts/app-all.js +++ b/public/dist/scripts/app-all.js @@ -4013,7 +4013,7 @@ this.rawPermissions=permissions;permissions.map(p=>{let{type,role}=this.parsePer if(existing===undefined){let newPermission={role,create:false,read:false,update:false,xdelete:false,};newPermission[type]=true;this.permissions.push(newPermission);} if(index!==-1){existing[type]=true;this.permissions[index]=existing;}});},addPermission(formId){if(this.permissions.length>0&&!this.validate(formId,this.permissions.length-1)){return;} this.permissions.push({role:'',create:false,read:false,update:false,xdelete:false,});},updatePermission(index){setTimeout(()=>{const permission=this.permissions[index];Object.keys(permission).forEach(key=>{if(key==='role'){return;} -const parsedKey=this.parseOutputPermission(key);const permissionString=this.buildPermission(parsedKey,permission.role);if(permission[key]){if(!this.rawPermissions.includes(permissionString)){this.rawPermissions.push(permissionString);}}else{this.rawPermissions=this.rawPermissions.filter(p=>{return!p.includes(permissionString);});}});});},removePermission(index){let row=this.permissions.splice(index,1);if(row.length===1){this.rawPermissions=this.rawPermissions.filter(p=>!p.includes(row[0].role));}},parsePermission(permission){let parts=permission.split('(');let type=parts[0];let role=parts[1].replace(')','').replace(' ','').replaceAll('"','');return{type,role};},buildPermission(type,role){return`${type}("${role}")`},parseInputPermission(key){if(key==='delete'){return'xdelete';} +const parsedKey=this.parseOutputPermission(key);const permissionString=this.buildPermission(parsedKey,permission.role);if(permission[key]){if(!this.rawPermissions.includes(permissionString)){this.rawPermissions.push(permissionString);}}else{this.rawPermissions=this.rawPermissions.filter(p=>{return!p.includes(permissionString);});}});});},clearPermission(index){let currentRole=this.permissions[index].role;this.rawPermissions=this.rawPermissions.filter(p=>{let{type,role}=this.parsePermission(p);return role!==currentRole;});},removePermission(index){let row=this.permissions.splice(index,1);if(row.length===1){this.rawPermissions=this.rawPermissions.filter(p=>!p.includes(row[0].role));}},parsePermission(permission){let parts=permission.split('(');let type=parts[0];let role=parts[1].replace(')','').replace(' ','').replaceAll('"','');return{type,role};},buildPermission(type,role){return`${type}("${role}")`},parseInputPermission(key){if(key==='delete'){return'xdelete';} return key;},parseOutputPermission(key){if(key==='xdelete'){return'delete';} return key;},validate(formId,index){const form=document.getElementById(formId);const input=document.getElementById(`${formId}Input${index}`);const permission=this.permissions[index];input.setCustomValidity('');if(permission.role===''){input.setCustomValidity('Role is required');}else if(!Object.entries(permission).some(([k,v])=>!k.includes('role')&&v)){input.setCustomValidity('No permissions selected');}else if(this.permissions.some(p=>p.role===permission.role&&p!==permission)){input.setCustomValidity('Role entry already exists');} return form.reportValidity();},prevent(event){event.preventDefault();event.stopPropagation();}}));});})(window);(function(window){"use strict";window.ls.view.add({selector:"data-service",controller:function(element,view,container,form,alerts,expression,window){let action=element.dataset["service"];let service=element.dataset["name"]||null;let event=expression.parse(element.dataset["event"]);let confirm=element.dataset["confirm"]||"";let loading=element.dataset["loading"]||"";let loaderId=null;let scope=element.dataset["scope"]||"sdk";let success=element.dataset["success"]||"";let failure=element.dataset["failure"]||"";let running=false;let callbacks={hide:function(){return function(){return element.style.opacity='0';};},reset:function(){return function(){if("FORM"===element.tagName){return element.reset();} diff --git a/public/dist/scripts/app.js b/public/dist/scripts/app.js index 40edce16e..bbdda3c91 100644 --- a/public/dist/scripts/app.js +++ b/public/dist/scripts/app.js @@ -607,7 +607,7 @@ this.rawPermissions=permissions;permissions.map(p=>{let{type,role}=this.parsePer if(existing===undefined){let newPermission={role,create:false,read:false,update:false,xdelete:false,};newPermission[type]=true;this.permissions.push(newPermission);} if(index!==-1){existing[type]=true;this.permissions[index]=existing;}});},addPermission(formId){if(this.permissions.length>0&&!this.validate(formId,this.permissions.length-1)){return;} this.permissions.push({role:'',create:false,read:false,update:false,xdelete:false,});},updatePermission(index){setTimeout(()=>{const permission=this.permissions[index];Object.keys(permission).forEach(key=>{if(key==='role'){return;} -const parsedKey=this.parseOutputPermission(key);const permissionString=this.buildPermission(parsedKey,permission.role);if(permission[key]){if(!this.rawPermissions.includes(permissionString)){this.rawPermissions.push(permissionString);}}else{this.rawPermissions=this.rawPermissions.filter(p=>{return!p.includes(permissionString);});}});});},removePermission(index){let row=this.permissions.splice(index,1);if(row.length===1){this.rawPermissions=this.rawPermissions.filter(p=>!p.includes(row[0].role));}},parsePermission(permission){let parts=permission.split('(');let type=parts[0];let role=parts[1].replace(')','').replace(' ','').replaceAll('"','');return{type,role};},buildPermission(type,role){return`${type}("${role}")`},parseInputPermission(key){if(key==='delete'){return'xdelete';} +const parsedKey=this.parseOutputPermission(key);const permissionString=this.buildPermission(parsedKey,permission.role);if(permission[key]){if(!this.rawPermissions.includes(permissionString)){this.rawPermissions.push(permissionString);}}else{this.rawPermissions=this.rawPermissions.filter(p=>{return!p.includes(permissionString);});}});});},clearPermission(index){let currentRole=this.permissions[index].role;this.rawPermissions=this.rawPermissions.filter(p=>{let{type,role}=this.parsePermission(p);return role!==currentRole;});},removePermission(index){let row=this.permissions.splice(index,1);if(row.length===1){this.rawPermissions=this.rawPermissions.filter(p=>!p.includes(row[0].role));}},parsePermission(permission){let parts=permission.split('(');let type=parts[0];let role=parts[1].replace(')','').replace(' ','').replaceAll('"','');return{type,role};},buildPermission(type,role){return`${type}("${role}")`},parseInputPermission(key){if(key==='delete'){return'xdelete';} return key;},parseOutputPermission(key){if(key==='xdelete'){return'delete';} return key;},validate(formId,index){const form=document.getElementById(formId);const input=document.getElementById(`${formId}Input${index}`);const permission=this.permissions[index];input.setCustomValidity('');if(permission.role===''){input.setCustomValidity('Role is required');}else if(!Object.entries(permission).some(([k,v])=>!k.includes('role')&&v)){input.setCustomValidity('No permissions selected');}else if(this.permissions.some(p=>p.role===permission.role&&p!==permission)){input.setCustomValidity('Role entry already exists');} return form.reportValidity();},prevent(event){event.preventDefault();event.stopPropagation();}}));});})(window);(function(window){"use strict";window.ls.view.add({selector:"data-service",controller:function(element,view,container,form,alerts,expression,window){let action=element.dataset["service"];let service=element.dataset["name"]||null;let event=expression.parse(element.dataset["event"]);let confirm=element.dataset["confirm"]||"";let loading=element.dataset["loading"]||"";let loaderId=null;let scope=element.dataset["scope"]||"sdk";let success=element.dataset["success"]||"";let failure=element.dataset["failure"]||"";let running=false;let callbacks={hide:function(){return function(){return element.style.opacity='0';};},reset:function(){return function(){if("FORM"===element.tagName){return element.reset();} diff --git a/public/scripts/permissions-matrix.js b/public/scripts/permissions-matrix.js index 81a869a8f..5196e1cbc 100644 --- a/public/scripts/permissions-matrix.js +++ b/public/scripts/permissions-matrix.js @@ -75,6 +75,14 @@ }); }); }, + clearPermission(index) { + let currentRole = this.permissions[index].role; + this.rawPermissions = this.rawPermissions.filter(p => { + let {type, role} = this.parsePermission(p); + + return role !== currentRole; + }); + }, removePermission(index) { let row = this.permissions.splice(index, 1); if (row.length === 1) { From 85ce646e0c9eed1fa7288848328ee746a4667155 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 20 Sep 2022 22:04:15 +1200 Subject: [PATCH 20/42] Add back prevent enter --- app/views/console/comps/permissions-matrix.phtml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/views/console/comps/permissions-matrix.phtml b/app/views/console/comps/permissions-matrix.phtml index 80a424fc4..e912f2662 100644 --- a/app/views/console/comps/permissions-matrix.phtml +++ b/app/views/console/comps/permissions-matrix.phtml @@ -81,6 +81,7 @@ $escapedPermissions = \array_map(function ($perm) { list="types" type="text" x-model="permission.role" + @keydown.enter="prevent($event)" @keydown="clearPermission(index)" @keyup="updatePermission(index)"/> From 398c3eec29d518568d8e4fba3d7fa5ffcc6cd133 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 22 Sep 2022 19:26:54 +1200 Subject: [PATCH 21/42] Fix SDK response model for get team membership --- app/controllers/api/teams.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index c97537022..801c647a2 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -553,7 +553,7 @@ App::get('/v1/teams/:teamId/memberships/:membershipId') ->label('sdk.description', '/docs/references/teams/get-team-member.md') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) - ->label('sdk.response.model', Response::MODEL_MEMBERSHIP_LIST) + ->label('sdk.response.model', Response::MODEL_MEMBERSHIP) ->param('teamId', '', new UID(), 'Team ID.') ->param('membershipId', '', new UID(), 'Membership ID.') ->inject('response') From 7f236ed2bb413a4ec7f0b0ff987f703b49decedf Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 22 Sep 2022 20:06:30 +1200 Subject: [PATCH 22/42] Add get membership tests --- tests/e2e/Services/Teams/TeamsBaseClient.php | 46 ++++++++++++++++++++ tests/e2e/Services/Teams/TeamsBaseServer.php | 45 +++++++++++++++++++ 2 files changed, 91 insertions(+) diff --git a/tests/e2e/Services/Teams/TeamsBaseClient.php b/tests/e2e/Services/Teams/TeamsBaseClient.php index 266bdefc8..c5ecf9f7f 100644 --- a/tests/e2e/Services/Teams/TeamsBaseClient.php +++ b/tests/e2e/Services/Teams/TeamsBaseClient.php @@ -126,6 +126,52 @@ trait TeamsBaseClient return $data; } + /** + * @depends testCreateTeamMembership + */ + public function testGetTeamMembership($data): void + { + $teamUid = $data['teamUid'] ?? ''; + $membershipUid = $data['membershipUid'] ?? ''; + + /** + * Test for SUCCESS + */ + $response = $this->client->call(Client::METHOD_GET, '/teams/' . $teamUid . '/memberships/' . $membershipUid, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertNotEmpty($response['body']['$id']); + $this->assertNotEmpty($response['body']['userId']); + $this->assertNotEmpty($response['body']['userName']); + $this->assertNotEmpty($response['body']['userEmail']); + $this->assertNotEmpty($response['body']['teamId']); + $this->assertNotEmpty($response['body']['teamName']); + $this->assertCount(2, $response['body']['roles']); + $this->assertEquals(false, DateTime::isValid($response['body']['joined'])); // is null in DB + $this->assertEquals(false, $response['body']['confirm']); + + /** + * Test for FAILURE + */ + + $response = $this->client->call(Client::METHOD_GET, '/teams/' . $teamUid . '/memberships/' . $membershipUid . 'dasdasd', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(404, $response['headers']['status-code']); + + $response = $this->client->call(Client::METHOD_GET, '/teams/' . $teamUid . '/memberships/' . $membershipUid, [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ]); + + $this->assertEquals(401, $response['headers']['status-code']); + } + /** * @depends testCreateTeam */ diff --git a/tests/e2e/Services/Teams/TeamsBaseServer.php b/tests/e2e/Services/Teams/TeamsBaseServer.php index 2e13c9ed3..df508e5e3 100644 --- a/tests/e2e/Services/Teams/TeamsBaseServer.php +++ b/tests/e2e/Services/Teams/TeamsBaseServer.php @@ -33,6 +33,51 @@ trait TeamsBaseServer return []; } + /** + * @depends testCreateTeamMembership + */ + public function testGetTeamMembership($data): void + { + $teamUid = $data['teamUid'] ?? ''; + $membershipUid = $data['membershipUid'] ?? ''; + + /** + * Test for SUCCESS + */ + $response = $this->client->call(Client::METHOD_GET, '/teams/' . $teamUid . '/memberships/' . $membershipUid, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertNotEmpty($response['body']['$id']); + $this->assertNotEmpty($response['body']['userId']); + $this->assertNotEmpty($response['body']['userName']); + $this->assertNotEmpty($response['body']['userEmail']); + $this->assertNotEmpty($response['body']['teamId']); + $this->assertNotEmpty($response['body']['teamName']); + $this->assertCount(2, $response['body']['roles']); + $this->assertEquals(true, DateTime::isValid($response['body']['joined'])); // is null in DB + $this->assertEquals(true, $response['body']['confirm']); + + /** + * Test for FAILURE + */ + + $response = $this->client->call(Client::METHOD_GET, '/teams/' . $teamUid . '/memberships/' . $membershipUid . 'dasdasd', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(404, $response['headers']['status-code']); + + $response = $this->client->call(Client::METHOD_GET, '/teams/' . $teamUid . '/memberships/' . $membershipUid, [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ]); + + $this->assertEquals(401, $response['headers']['status-code']); + } /** * @depends testCreateTeam From 01590796080a0d534915c45a0e83867d80102409 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Fri, 23 Sep 2022 06:12:17 +0000 Subject: [PATCH 23/42] Fix schedule loop triggering --- app/controllers/api/functions.php | 46 +++++++++++++++---------------- app/workers/functions.php | 7 +++++ 2 files changed, 30 insertions(+), 23 deletions(-) diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index f42d1059e..4fd609084 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -71,8 +71,13 @@ App::post('/v1/functions') ->param('enabled', true, new Boolean(), 'Is function enabled?', true) ->inject('response') ->inject('dbForProject') + ->inject('project') + ->inject('user') ->inject('events') - ->action(function (string $functionId, string $name, array $execute, string $runtime, array $events, string $schedule, int $timeout, bool $enabled, Response $response, Database $dbForProject, Event $eventsInstance) { + ->action(function (string $functionId, string $name, array $execute, string $runtime, array $events, string $schedule, int $timeout, bool $enabled, Response $response, Database $dbForProject, Document $project, Document $user, Event $eventsInstance) { + + $cron = !empty($schedule) ? new CronExpression($schedule) : null; + $next = !empty($schedule) ? DateTime::format($cron->getNextRunDate()) : null; $functionId = ($functionId == 'unique()') ? ID::unique() : $functionId; $function = $dbForProject->createDocument('functions', new Document([ @@ -86,11 +91,22 @@ App::post('/v1/functions') 'schedule' => $schedule, 'scheduleUpdatedAt' => DateTime::now(), 'schedulePrevious' => null, - 'scheduleNext' => null, + 'scheduleNext' => $next, 'timeout' => $timeout, 'search' => implode(' ', [$functionId, $name, $runtime]) ])); + if ($next) { + // Async task reschedule + $functionEvent = new Func(); + $functionEvent + ->setFunction($function) + ->setType('schedule') + ->setUser($user) + ->setProject($project) + ->schedule(new \DateTime($next)); + } + $eventsInstance->setParam('functionId', $function->getId()); $response @@ -442,11 +458,9 @@ App::put('/v1/functions/:functionId') throw new Exception(Exception::FUNCTION_NOT_FOUND); } - $original = $function->getAttribute('schedule', ''); - $cron = (!empty($function->getAttribute('deployment')) && !empty($schedule)) ? new CronExpression($schedule) : null; - $next = (!empty($function->getAttribute('deployment')) && !empty($schedule)) ? DateTime::format($cron->getNextRunDate()) : null; + $cron = !empty($schedule) ? new CronExpression($schedule) : null; + $next = !empty($schedule) ? DateTime::format($cron->getNextRunDate()) : null; - $scheduleUpdatedAt = $schedule !== $original ? DateTime::now() : $function->getAttribute('scheduleUpdatedAt'); $enabled ??= $function->getAttribute('enabled', true); $function = $dbForProject->updateDocument('functions', $function->getId(), new Document(array_merge($function->getArrayCopy(), [ @@ -454,14 +468,14 @@ App::put('/v1/functions/:functionId') 'name' => $name, 'events' => $events, 'schedule' => $schedule, - 'scheduleUpdatedAt' => $scheduleUpdatedAt, + 'scheduleUpdatedAt' => DateTime::now(), 'scheduleNext' => $next, 'timeout' => $timeout, 'enabled' => $enabled, 'search' => implode(' ', [$functionId, $name, $function->getAttribute('runtime')]), ]))); - if ($next && $schedule !== $original) { + if ($next) { // Async task reschedule $functionEvent = new Func(); $functionEvent @@ -519,24 +533,10 @@ App::patch('/v1/functions/:functionId/deployments/:deploymentId') throw new Exception(Exception::BUILD_NOT_READY); } - $schedule = $function->getAttribute('schedule', ''); - $cron = (empty($function->getAttribute('deployment')) && !empty($schedule)) ? new CronExpression($schedule) : null; - $next = (empty($function->getAttribute('deployment')) && !empty($schedule)) ? DateTime::format($cron->getNextRunDate()) : null; - $function = $dbForProject->updateDocument('functions', $function->getId(), new Document(array_merge($function->getArrayCopy(), [ - 'deployment' => $deployment->getId(), - 'scheduleNext' => $next, + 'deployment' => $deployment->getId() ]))); - if ($next) { // Init first schedule - $functionEvent = new Func(); - $functionEvent - ->setType('schedule') - ->setFunction($function) - ->setProject($project) - ->schedule(new \DateTime($next)); - } - $events ->setParam('functionId', $function->getId()) ->setParam('deploymentId', $deployment->getId()); diff --git a/app/workers/functions.php b/app/workers/functions.php index 31b74a11c..234ed5663 100644 --- a/app/workers/functions.php +++ b/app/workers/functions.php @@ -150,14 +150,21 @@ class FunctionsV1 extends Worker throw new Exception('Function not found (' . $function->getId() . ')'); } + \var_dump("Got here"); + if ($functionOriginal->getAttribute('schedule') !== $function->getAttribute('schedule')) { // Schedule has changed from previous run, ignore this run. return; } + + \var_dump("Got here 2"); + if ($functionOriginal->getAttribute('scheduleUpdatedAt') !== $function->getAttribute('scheduleUpdatedAt')) { // Double execution due to rapid cron changes, ignore this run. return; } + \var_dump("Got here 3"); + $cron = new CronExpression($function->getAttribute('schedule')); $next = DateTime::format($cron->getNextRunDate()); From e850be40b2439943365685276cdcd00343a844c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Fri, 23 Sep 2022 06:21:10 +0000 Subject: [PATCH 24/42] Cleanup --- app/workers/functions.php | 7 ------- 1 file changed, 7 deletions(-) diff --git a/app/workers/functions.php b/app/workers/functions.php index 234ed5663..31b74a11c 100644 --- a/app/workers/functions.php +++ b/app/workers/functions.php @@ -150,21 +150,14 @@ class FunctionsV1 extends Worker throw new Exception('Function not found (' . $function->getId() . ')'); } - \var_dump("Got here"); - if ($functionOriginal->getAttribute('schedule') !== $function->getAttribute('schedule')) { // Schedule has changed from previous run, ignore this run. return; } - - \var_dump("Got here 2"); - if ($functionOriginal->getAttribute('scheduleUpdatedAt') !== $function->getAttribute('scheduleUpdatedAt')) { // Double execution due to rapid cron changes, ignore this run. return; } - \var_dump("Got here 3"); - $cron = new CronExpression($function->getAttribute('schedule')); $next = DateTime::format($cron->getNextRunDate()); From 69846749bf3aaa6aa9844b58ec3267d36ac1ff67 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Fri, 23 Sep 2022 06:31:43 +0000 Subject: [PATCH 25/42] update changelogs --- docs/sdks/dart/CHANGELOG.md | 11 ++++++++--- docs/sdks/flutter/CHANGELOG.md | 10 +++++++--- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/docs/sdks/dart/CHANGELOG.md b/docs/sdks/dart/CHANGELOG.md index 39f44c1d9..a17fb5bba 100644 --- a/docs/sdks/dart/CHANGELOG.md +++ b/docs/sdks/dart/CHANGELOG.md @@ -1,6 +1,11 @@ -## 7.0.0-dev.2 +## 7.1.0 + +* Role helper update + +## 7.0.0 + ### NEW -* Support for Appwrite 1.0.0-RC1 +* Support for Appwrite 1.0.0 * More verbose headers have been included in the Clients - `x-sdk-name`, `x-sdk-platform`, `x-sdk-language`, `x-sdk-version` * Helper classes and methods for Permissions, Roles and IDs * Helper methods to suport new queries @@ -26,7 +31,7 @@ 3. `greater` renamed to `greaterThan` 4. `greaterEqual` renamed to `greaterThanEqual` -**Full Changelog for Appwrite 1.0.0-RC1 can be found here**: https://github.com/appwrite/appwrite/blob/master/CHANGES.md +**Full Changelog for Appwrite 1.0.0 can be found here**: https://github.com/appwrite/appwrite/blob/master/CHANGES.md ## 6.0.1 * Dependency upgrades diff --git a/docs/sdks/flutter/CHANGELOG.md b/docs/sdks/flutter/CHANGELOG.md index 3424e8d19..0338da833 100644 --- a/docs/sdks/flutter/CHANGELOG.md +++ b/docs/sdks/flutter/CHANGELOG.md @@ -1,7 +1,11 @@ -## 8.0.0-dev.2 Latest +## 8.1.0 + +* Role helper update + +## 8.0.0 ### NEW -* Support for Appwrite 1.0.0-RC1 +* Support for Appwrite 1.0.0 * More verbose headers have been included in the Clients - `x-sdk-name`, `x-sdk-platform`, `x-sdk-language`, `x-sdk-version` * Helper classes and methods for Permissions, Roles and IDs * Helper methods to suport new queries @@ -21,7 +25,7 @@ 4. `greaterEqual` renamed to `greaterThanEqual` * `User` response model is now renamed to `Account` -**Full Changelog for Appwrite 1.0.0-RC1 can be found here**: +**Full Changelog for Appwrite 1.0.0 can be found here**: https://github.com/appwrite/appwrite/blob/master/CHANGES.md ## 7.0.0 From 8cc768cc50283ddfd773292e84d22b3e9588b5e4 Mon Sep 17 00:00:00 2001 From: Steven Nguyen Date: Tue, 27 Sep 2022 19:48:35 +0000 Subject: [PATCH 26/42] Update Console to Show Function Stdout Logs Although there is still a known issue where function stdout logs can appear in the wrong execution, not having the logs easily accessible in the console makes troubleshooting functions significantly more difficult. --- app/views/console/functions/function.phtml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/views/console/functions/function.phtml b/app/views/console/functions/function.phtml index 80806fed5..46ac7a4db 100644 --- a/app/views/console/functions/function.phtml +++ b/app/views/console/functions/function.phtml @@ -391,9 +391,9 @@ sort($patterns); Created - Status - Trigger - Runtime + Status + Trigger + Runtime @@ -422,11 +422,11 @@ sort($patterns);
- + - +