diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
new file mode 100644
index 000000000..95188b5fa
--- /dev/null
+++ b/.github/workflows/tests.yml
@@ -0,0 +1,70 @@
+name: CI Workflow
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+on: [pull_request]
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ php-version: ['8.1']
+ sdk: [Android11Java8, Android11Java11, Android12Java8, Android12Java11, CLINode14, CLINode16, DartBeta, DartStable, Deno1193, Deno1303, DotNet60, DotNet70, FlutterStable, FlutterBeta, Go112, Go118, KotlinJava8, KotlinJava11, KotlinJava17, Node12, Node14, Node16, PHP74, PHP80, Python38, Python39, Python310, Ruby27, Ruby30, Ruby31, AppleSwift55, Swift55, WebChromium, WebNode]
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v3
+ with:
+ submodules: recursive
+
+ - name: Docker Setup Buildx
+ uses: docker/setup-buildx-action@v3.0.0
+
+ - name: Setup PHP with PECL extension
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php-version }}
+ extensions: curl
+
+ - name: Before Install
+ run: |
+ if [ ! -z "${DOCKERHUB_PULL_USERNAME:-}" ]; then
+ echo "${DOCKERHUB_PULL_PASSWORD}" | docker login --username "${DOCKERHUB_PULL_USERNAME}" --password-stdin
+ fi
+
+ - name: Install
+ run: |
+ docker --version
+ composer install
+
+ - name: Lint
+ if: matrix.sdk == 'Lint'
+ run: |
+ composer lint
+
+ - name: Run Tests
+ run: |
+ composer test tests/${{ matrix.sdk }}Test.php
+
+ lint:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v3
+
+ - name: Setup PHP with PECL extension
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php-version }}
+ extensions: curl
+
+ - name: Install
+ run: composer install
+
+ - name: Lint
+ run: composer lint
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 663053727..000000000
--- a/.travis.yml
+++ /dev/null
@@ -1,79 +0,0 @@
-
-dist: focal
-
-arch:
- - amd64
-
-services:
- - docker
-
-os: linux
-
-language: php
-
-php:
- - 8.1
-
-stages:
- - lint
- - test
-
-env:
- - SDK=Android11Java8
- - SDK=Android11Java11
- - SDK=Android12Java8
- - SDK=Android12Java11
- - SDK=CLINode14
- - SDK=CLINode16
- - SDK=DartBeta
- - SDK=DartStable
- - SDK=Deno1193
- - SDK=Deno1303
- - SDK=DotNet60
- - SDK=DotNet70
- - SDK=FlutterStable
- - SDK=FlutterBeta
- - SDK=Go112
- - SDK=Go118
- - SDK=KotlinJava8
- - SDK=KotlinJava11
- - SDK=KotlinJava17
- - SDK=Node12
- - SDK=Node14
- - SDK=Node16
- - SDK=PHP74
- - SDK=PHP80
- - SDK=Python38
- - SDK=Python39
- - SDK=Python310
- - SDK=Ruby27
- - SDK=Ruby30
- - SDK=Ruby31
- - SDK=AppleSwift55
- - SDK=Swift55
- - SDK=WebChromium
- - SDK=WebNode
-
-notifications:
- email:
- - team@appwrite.io
-
-jobs:
- include:
- - stage: lint
- name: Lint
- install:
- - composer install
- script:
- - composer lint
-
-before_install:
- - >
- if [ ! -z "${DOCKERHUB_PULL_USERNAME:-}" ]; then
- echo "${DOCKERHUB_PULL_PASSWORD}" | docker login --username "${DOCKERHUB_PULL_USERNAME}" --password-stdin
- fi
-install:
- - docker --version
- - composer install
-script:
- - composer test tests/${SDK}Test.php
\ No newline at end of file
diff --git a/composer.lock b/composer.lock
index eb0e7c2a9..8f0e09370 100644
--- a/composer.lock
+++ b/composer.lock
@@ -8,16 +8,16 @@
"packages": [
{
"name": "matthiasmullie/minify",
- "version": "1.3.70",
+ "version": "1.3.71",
"source": {
"type": "git",
"url": "https://github.com/matthiasmullie/minify.git",
- "reference": "2807d9f9bece6877577ad44acb5c801bb3ae536b"
+ "reference": "ae42a47d7fecc1fbb7277b2f2d84c37a33edc3b1"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/matthiasmullie/minify/zipball/2807d9f9bece6877577ad44acb5c801bb3ae536b",
- "reference": "2807d9f9bece6877577ad44acb5c801bb3ae536b",
+ "url": "https://api.github.com/repos/matthiasmullie/minify/zipball/ae42a47d7fecc1fbb7277b2f2d84c37a33edc3b1",
+ "reference": "ae42a47d7fecc1fbb7277b2f2d84c37a33edc3b1",
"shasum": ""
},
"require": {
@@ -67,7 +67,7 @@
],
"support": {
"issues": "https://github.com/matthiasmullie/minify/issues",
- "source": "https://github.com/matthiasmullie/minify/tree/1.3.70"
+ "source": "https://github.com/matthiasmullie/minify/tree/1.3.71"
},
"funding": [
{
@@ -75,7 +75,7 @@
"type": "github"
}
],
- "time": "2022-12-09T12:56:44+00:00"
+ "time": "2023-04-25T20:33:03+00:00"
},
{
"name": "matthiasmullie/path-converter",
@@ -132,16 +132,16 @@
},
{
"name": "symfony/polyfill-ctype",
- "version": "v1.27.0",
+ "version": "v1.28.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
- "reference": "5bbc823adecdae860bb64756d639ecfec17b050a"
+ "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/5bbc823adecdae860bb64756d639ecfec17b050a",
- "reference": "5bbc823adecdae860bb64756d639ecfec17b050a",
+ "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb",
+ "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb",
"shasum": ""
},
"require": {
@@ -156,7 +156,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-main": "1.27-dev"
+ "dev-main": "1.28-dev"
},
"thanks": {
"name": "symfony/polyfill",
@@ -194,7 +194,7 @@
"portable"
],
"support": {
- "source": "https://github.com/symfony/polyfill-ctype/tree/v1.27.0"
+ "source": "https://github.com/symfony/polyfill-ctype/tree/v1.28.0"
},
"funding": [
{
@@ -210,20 +210,20 @@
"type": "tidelift"
}
],
- "time": "2022-11-03T14:55:06+00:00"
+ "time": "2023-01-26T09:26:14+00:00"
},
{
"name": "symfony/polyfill-mbstring",
- "version": "v1.27.0",
+ "version": "v1.28.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
- "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534"
+ "reference": "42292d99c55abe617799667f454222c54c60e229"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/8ad114f6b39e2c98a8b0e3bd907732c207c2b534",
- "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534",
+ "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/42292d99c55abe617799667f454222c54c60e229",
+ "reference": "42292d99c55abe617799667f454222c54c60e229",
"shasum": ""
},
"require": {
@@ -238,7 +238,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-main": "1.27-dev"
+ "dev-main": "1.28-dev"
},
"thanks": {
"name": "symfony/polyfill",
@@ -277,7 +277,7 @@
"shim"
],
"support": {
- "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.27.0"
+ "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.28.0"
},
"funding": [
{
@@ -293,20 +293,20 @@
"type": "tidelift"
}
],
- "time": "2022-11-03T14:55:06+00:00"
+ "time": "2023-07-28T09:04:16+00:00"
},
{
"name": "twig/twig",
- "version": "v3.5.1",
+ "version": "v3.7.1",
"source": {
"type": "git",
"url": "https://github.com/twigphp/Twig.git",
- "reference": "a6e0510cc793912b451fd40ab983a1d28f611c15"
+ "reference": "a0ce373a0ca3bf6c64b9e3e2124aca502ba39554"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/twigphp/Twig/zipball/a6e0510cc793912b451fd40ab983a1d28f611c15",
- "reference": "a6e0510cc793912b451fd40ab983a1d28f611c15",
+ "url": "https://api.github.com/repos/twigphp/Twig/zipball/a0ce373a0ca3bf6c64b9e3e2124aca502ba39554",
+ "reference": "a0ce373a0ca3bf6c64b9e3e2124aca502ba39554",
"shasum": ""
},
"require": {
@@ -315,15 +315,10 @@
"symfony/polyfill-mbstring": "^1.3"
},
"require-dev": {
- "psr/container": "^1.0",
- "symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0"
+ "psr/container": "^1.0|^2.0",
+ "symfony/phpunit-bridge": "^5.4.9|^6.3"
},
"type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "3.5-dev"
- }
- },
"autoload": {
"psr-4": {
"Twig\\": "src/"
@@ -357,7 +352,7 @@
],
"support": {
"issues": "https://github.com/twigphp/Twig/issues",
- "source": "https://github.com/twigphp/Twig/tree/v3.5.1"
+ "source": "https://github.com/twigphp/Twig/tree/v3.7.1"
},
"funding": [
{
@@ -369,22 +364,22 @@
"type": "tidelift"
}
],
- "time": "2023-02-08T07:49:20+00:00"
+ "time": "2023-08-28T11:09:02+00:00"
}
],
"packages-dev": [
{
"name": "brianium/paratest",
- "version": "v6.9.1",
+ "version": "v6.10.1",
"source": {
"type": "git",
"url": "https://github.com/paratestphp/paratest.git",
- "reference": "51691208db882922c55d6c465be3e7d95028c449"
+ "reference": "d6f32a91302b74458e8ef5d132bb2215a5edb34b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/paratestphp/paratest/zipball/51691208db882922c55d6c465be3e7d95028c449",
- "reference": "51691208db882922c55d6c465be3e7d95028c449",
+ "url": "https://api.github.com/repos/paratestphp/paratest/zipball/d6f32a91302b74458e8ef5d132bb2215a5edb34b",
+ "reference": "d6f32a91302b74458e8ef5d132bb2215a5edb34b",
"shasum": ""
},
"require": {
@@ -451,7 +446,7 @@
],
"support": {
"issues": "https://github.com/paratestphp/paratest/issues",
- "source": "https://github.com/paratestphp/paratest/tree/v6.9.1"
+ "source": "https://github.com/paratestphp/paratest/tree/v6.10.1"
},
"funding": [
{
@@ -463,7 +458,7 @@
"type": "paypal"
}
],
- "time": "2023-03-03T09:35:17+00:00"
+ "time": "2023-10-04T13:33:07+00:00"
},
{
"name": "doctrine/instantiator",
@@ -716,16 +711,16 @@
},
{
"name": "nikic/php-parser",
- "version": "v4.15.4",
+ "version": "v4.17.1",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
- "reference": "6bb5176bc4af8bcb7d926f88718db9b96a2d4290"
+ "reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/6bb5176bc4af8bcb7d926f88718db9b96a2d4290",
- "reference": "6bb5176bc4af8bcb7d926f88718db9b96a2d4290",
+ "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d",
+ "reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d",
"shasum": ""
},
"require": {
@@ -766,9 +761,9 @@
],
"support": {
"issues": "https://github.com/nikic/PHP-Parser/issues",
- "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.4"
+ "source": "https://github.com/nikic/PHP-Parser/tree/v4.17.1"
},
- "time": "2023-03-05T19:49:14+00:00"
+ "time": "2023-08-13T19:53:39+00:00"
},
{
"name": "phar-io/manifest",
@@ -883,16 +878,16 @@
},
{
"name": "phpunit/php-code-coverage",
- "version": "9.2.26",
+ "version": "9.2.29",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
- "reference": "443bc6912c9bd5b409254a40f4b0f4ced7c80ea1"
+ "reference": "6a3a87ac2bbe33b25042753df8195ba4aa534c76"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/443bc6912c9bd5b409254a40f4b0f4ced7c80ea1",
- "reference": "443bc6912c9bd5b409254a40f4b0f4ced7c80ea1",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/6a3a87ac2bbe33b25042753df8195ba4aa534c76",
+ "reference": "6a3a87ac2bbe33b25042753df8195ba4aa534c76",
"shasum": ""
},
"require": {
@@ -948,7 +943,8 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
- "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.26"
+ "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy",
+ "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.29"
},
"funding": [
{
@@ -956,7 +952,7 @@
"type": "github"
}
],
- "time": "2023-03-06T12:58:08+00:00"
+ "time": "2023-09-19T04:57:46+00:00"
},
{
"name": "phpunit/php-file-iterator",
@@ -1201,16 +1197,16 @@
},
{
"name": "phpunit/phpunit",
- "version": "9.6.6",
+ "version": "9.6.13",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
- "reference": "b65d59a059d3004a040c16a82e07bbdf6cfdd115"
+ "reference": "f3d767f7f9e191eab4189abe41ab37797e30b1be"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/b65d59a059d3004a040c16a82e07bbdf6cfdd115",
- "reference": "b65d59a059d3004a040c16a82e07bbdf6cfdd115",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/f3d767f7f9e191eab4189abe41ab37797e30b1be",
+ "reference": "f3d767f7f9e191eab4189abe41ab37797e30b1be",
"shasum": ""
},
"require": {
@@ -1225,7 +1221,7 @@
"phar-io/manifest": "^2.0.3",
"phar-io/version": "^3.0.2",
"php": ">=7.3",
- "phpunit/php-code-coverage": "^9.2.13",
+ "phpunit/php-code-coverage": "^9.2.28",
"phpunit/php-file-iterator": "^3.0.5",
"phpunit/php-invoker": "^3.1.1",
"phpunit/php-text-template": "^2.0.3",
@@ -1284,7 +1280,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
- "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.6"
+ "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.13"
},
"funding": [
{
@@ -1300,7 +1296,7 @@
"type": "tidelift"
}
],
- "time": "2023-03-27T11:43:46+00:00"
+ "time": "2023-09-19T05:39:22+00:00"
},
{
"name": "psr/container",
@@ -1655,16 +1651,16 @@
},
{
"name": "sebastian/diff",
- "version": "4.0.4",
+ "version": "4.0.5",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/diff.git",
- "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d"
+ "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3461e3fccc7cfdfc2720be910d3bd73c69be590d",
- "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d",
+ "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/74be17022044ebaaecfdf0c5cd504fc9cd5a7131",
+ "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131",
"shasum": ""
},
"require": {
@@ -1709,7 +1705,7 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/diff/issues",
- "source": "https://github.com/sebastianbergmann/diff/tree/4.0.4"
+ "source": "https://github.com/sebastianbergmann/diff/tree/4.0.5"
},
"funding": [
{
@@ -1717,7 +1713,7 @@
"type": "github"
}
],
- "time": "2020-10-26T13:10:38+00:00"
+ "time": "2023-05-07T05:35:17+00:00"
},
{
"name": "sebastian/environment",
@@ -1861,16 +1857,16 @@
},
{
"name": "sebastian/global-state",
- "version": "5.0.5",
+ "version": "5.0.6",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/global-state.git",
- "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2"
+ "reference": "bde739e7565280bda77be70044ac1047bc007e34"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/0ca8db5a5fc9c8646244e629625ac486fa286bf2",
- "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2",
+ "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bde739e7565280bda77be70044ac1047bc007e34",
+ "reference": "bde739e7565280bda77be70044ac1047bc007e34",
"shasum": ""
},
"require": {
@@ -1913,7 +1909,7 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/global-state/issues",
- "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.5"
+ "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.6"
},
"funding": [
{
@@ -1921,7 +1917,7 @@
"type": "github"
}
],
- "time": "2022-02-14T08:28:10+00:00"
+ "time": "2023-08-02T09:26:13+00:00"
},
{
"name": "sebastian/lines-of-code",
@@ -2378,23 +2374,23 @@
},
{
"name": "symfony/console",
- "version": "v6.2.8",
+ "version": "v6.3.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
- "reference": "3582d68a64a86ec25240aaa521ec8bc2342b369b"
+ "reference": "eca495f2ee845130855ddf1cf18460c38966c8b6"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/console/zipball/3582d68a64a86ec25240aaa521ec8bc2342b369b",
- "reference": "3582d68a64a86ec25240aaa521ec8bc2342b369b",
+ "url": "https://api.github.com/repos/symfony/console/zipball/eca495f2ee845130855ddf1cf18460c38966c8b6",
+ "reference": "eca495f2ee845130855ddf1cf18460c38966c8b6",
"shasum": ""
},
"require": {
"php": ">=8.1",
- "symfony/deprecation-contracts": "^2.1|^3",
+ "symfony/deprecation-contracts": "^2.5|^3",
"symfony/polyfill-mbstring": "~1.0",
- "symfony/service-contracts": "^1.1|^2|^3",
+ "symfony/service-contracts": "^2.5|^3",
"symfony/string": "^5.4|^6.0"
},
"conflict": {
@@ -2416,12 +2412,6 @@
"symfony/process": "^5.4|^6.0",
"symfony/var-dumper": "^5.4|^6.0"
},
- "suggest": {
- "psr/log": "For using the console logger",
- "symfony/event-dispatcher": "",
- "symfony/lock": "",
- "symfony/process": ""
- },
"type": "library",
"autoload": {
"psr-4": {
@@ -2454,7 +2444,7 @@
"terminal"
],
"support": {
- "source": "https://github.com/symfony/console/tree/v6.2.8"
+ "source": "https://github.com/symfony/console/tree/v6.3.4"
},
"funding": [
{
@@ -2470,20 +2460,20 @@
"type": "tidelift"
}
],
- "time": "2023-03-29T21:42:15+00:00"
+ "time": "2023-08-16T10:10:12+00:00"
},
{
"name": "symfony/deprecation-contracts",
- "version": "v3.2.1",
+ "version": "v3.3.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/deprecation-contracts.git",
- "reference": "e2d1534420bd723d0ef5aec58a22c5fe60ce6f5e"
+ "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/e2d1534420bd723d0ef5aec58a22c5fe60ce6f5e",
- "reference": "e2d1534420bd723d0ef5aec58a22c5fe60ce6f5e",
+ "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/7c3aff79d10325257a001fcf92d991f24fc967cf",
+ "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf",
"shasum": ""
},
"require": {
@@ -2492,7 +2482,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-main": "3.3-dev"
+ "dev-main": "3.4-dev"
},
"thanks": {
"name": "symfony/contracts",
@@ -2521,7 +2511,7 @@
"description": "A generic function and convention to trigger deprecation notices",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/deprecation-contracts/tree/v3.2.1"
+ "source": "https://github.com/symfony/deprecation-contracts/tree/v3.3.0"
},
"funding": [
{
@@ -2537,20 +2527,20 @@
"type": "tidelift"
}
],
- "time": "2023-03-01T10:25:55+00:00"
+ "time": "2023-05-23T14:45:45+00:00"
},
{
"name": "symfony/polyfill-intl-grapheme",
- "version": "v1.27.0",
+ "version": "v1.28.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-grapheme.git",
- "reference": "511a08c03c1960e08a883f4cffcacd219b758354"
+ "reference": "875e90aeea2777b6f135677f618529449334a612"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/511a08c03c1960e08a883f4cffcacd219b758354",
- "reference": "511a08c03c1960e08a883f4cffcacd219b758354",
+ "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/875e90aeea2777b6f135677f618529449334a612",
+ "reference": "875e90aeea2777b6f135677f618529449334a612",
"shasum": ""
},
"require": {
@@ -2562,7 +2552,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-main": "1.27-dev"
+ "dev-main": "1.28-dev"
},
"thanks": {
"name": "symfony/polyfill",
@@ -2602,7 +2592,7 @@
"shim"
],
"support": {
- "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.27.0"
+ "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.28.0"
},
"funding": [
{
@@ -2618,20 +2608,20 @@
"type": "tidelift"
}
],
- "time": "2022-11-03T14:55:06+00:00"
+ "time": "2023-01-26T09:26:14+00:00"
},
{
"name": "symfony/polyfill-intl-normalizer",
- "version": "v1.27.0",
+ "version": "v1.28.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-normalizer.git",
- "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6"
+ "reference": "8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/19bd1e4fcd5b91116f14d8533c57831ed00571b6",
- "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6",
+ "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92",
+ "reference": "8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92",
"shasum": ""
},
"require": {
@@ -2643,7 +2633,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-main": "1.27-dev"
+ "dev-main": "1.28-dev"
},
"thanks": {
"name": "symfony/polyfill",
@@ -2686,7 +2676,7 @@
"shim"
],
"support": {
- "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.27.0"
+ "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.28.0"
},
"funding": [
{
@@ -2702,20 +2692,20 @@
"type": "tidelift"
}
],
- "time": "2022-11-03T14:55:06+00:00"
+ "time": "2023-01-26T09:26:14+00:00"
},
{
"name": "symfony/process",
- "version": "v6.2.8",
+ "version": "v6.3.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
- "reference": "75ed64103df4f6615e15a7fe38b8111099f47416"
+ "reference": "0b5c29118f2e980d455d2e34a5659f4579847c54"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/process/zipball/75ed64103df4f6615e15a7fe38b8111099f47416",
- "reference": "75ed64103df4f6615e15a7fe38b8111099f47416",
+ "url": "https://api.github.com/repos/symfony/process/zipball/0b5c29118f2e980d455d2e34a5659f4579847c54",
+ "reference": "0b5c29118f2e980d455d2e34a5659f4579847c54",
"shasum": ""
},
"require": {
@@ -2747,7 +2737,7 @@
"description": "Executes commands in sub-processes",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/process/tree/v6.2.8"
+ "source": "https://github.com/symfony/process/tree/v6.3.4"
},
"funding": [
{
@@ -2763,20 +2753,20 @@
"type": "tidelift"
}
],
- "time": "2023-03-09T16:20:02+00:00"
+ "time": "2023-08-07T10:39:22+00:00"
},
{
"name": "symfony/service-contracts",
- "version": "v3.2.1",
+ "version": "v3.3.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/service-contracts.git",
- "reference": "a8c9cedf55f314f3a186041d19537303766df09a"
+ "reference": "40da9cc13ec349d9e4966ce18b5fbcd724ab10a4"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/service-contracts/zipball/a8c9cedf55f314f3a186041d19537303766df09a",
- "reference": "a8c9cedf55f314f3a186041d19537303766df09a",
+ "url": "https://api.github.com/repos/symfony/service-contracts/zipball/40da9cc13ec349d9e4966ce18b5fbcd724ab10a4",
+ "reference": "40da9cc13ec349d9e4966ce18b5fbcd724ab10a4",
"shasum": ""
},
"require": {
@@ -2786,13 +2776,10 @@
"conflict": {
"ext-psr": "<1.1|>=2"
},
- "suggest": {
- "symfony/service-implementation": ""
- },
"type": "library",
"extra": {
"branch-alias": {
- "dev-main": "3.3-dev"
+ "dev-main": "3.4-dev"
},
"thanks": {
"name": "symfony/contracts",
@@ -2832,7 +2819,7 @@
"standards"
],
"support": {
- "source": "https://github.com/symfony/service-contracts/tree/v3.2.1"
+ "source": "https://github.com/symfony/service-contracts/tree/v3.3.0"
},
"funding": [
{
@@ -2848,20 +2835,20 @@
"type": "tidelift"
}
],
- "time": "2023-03-01T10:32:47+00:00"
+ "time": "2023-05-23T14:45:45+00:00"
},
{
"name": "symfony/string",
- "version": "v6.2.8",
+ "version": "v6.3.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
- "reference": "193e83bbd6617d6b2151c37fff10fa7168ebddef"
+ "reference": "13d76d0fb049051ed12a04bef4f9de8715bea339"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/string/zipball/193e83bbd6617d6b2151c37fff10fa7168ebddef",
- "reference": "193e83bbd6617d6b2151c37fff10fa7168ebddef",
+ "url": "https://api.github.com/repos/symfony/string/zipball/13d76d0fb049051ed12a04bef4f9de8715bea339",
+ "reference": "13d76d0fb049051ed12a04bef4f9de8715bea339",
"shasum": ""
},
"require": {
@@ -2872,13 +2859,13 @@
"symfony/polyfill-mbstring": "~1.0"
},
"conflict": {
- "symfony/translation-contracts": "<2.0"
+ "symfony/translation-contracts": "<2.5"
},
"require-dev": {
"symfony/error-handler": "^5.4|^6.0",
"symfony/http-client": "^5.4|^6.0",
"symfony/intl": "^6.2",
- "symfony/translation-contracts": "^2.0|^3.0",
+ "symfony/translation-contracts": "^2.5|^3.0",
"symfony/var-exporter": "^5.4|^6.0"
},
"type": "library",
@@ -2918,7 +2905,7 @@
"utf8"
],
"support": {
- "source": "https://github.com/symfony/string/tree/v6.2.8"
+ "source": "https://github.com/symfony/string/tree/v6.3.5"
},
"funding": [
{
@@ -2934,7 +2921,7 @@
"type": "tidelift"
}
],
- "time": "2023-03-20T16:06:02+00:00"
+ "time": "2023-09-18T10:38:32+00:00"
},
{
"name": "theseer/tokenizer",
@@ -2999,5 +2986,5 @@
"ext-json": "*"
},
"platform-dev": [],
- "plugin-api-version": "2.3.0"
+ "plugin-api-version": "2.6.0"
}
diff --git a/mock-server/.gitignore b/mock-server/.gitignore
new file mode 100644
index 000000000..3e61b0a96
--- /dev/null
+++ b/mock-server/.gitignore
@@ -0,0 +1,27 @@
+.idea/
+.dart_tool
+/vendor/
+/tests/sdks
+/.vscode
+.vs
+.phpunit.*
+.env
+.envrc
+.hatch
+
+# exclude everything
+examples/*
+tests/tmp
+.phpunit.result.cache
+
+# exception to the rule
+!examples/.gitkeep
+
+**/.DS_Store
+templates/swift/example/.build
+templates/swift/example/Example.xcodeproj/project.xcworkspace/xcuserdata
+templates/swift/example/Example.xcodeproj/xcuserdata
+**/xcuserdata
+# exclude go checksum files
+go.sum
+
diff --git a/mock-server/Dockerfile b/mock-server/Dockerfile
new file mode 100644
index 000000000..456aa530e
--- /dev/null
+++ b/mock-server/Dockerfile
@@ -0,0 +1,35 @@
+FROM composer:2.0 as composer
+
+LABEL maintainer="team@appwrite.io"
+
+ARG TESTING=false
+ENV TESTING=$TESTING
+
+WORKDIR /usr/local/src/
+
+COPY composer.lock /usr/local/src/
+COPY composer.json /usr/local/src/
+
+RUN composer install --ignore-platform-reqs --optimize-autoloader \
+ --no-plugins --no-scripts --prefer-dist \
+ `if [ "$TESTING" != "true" ]; then echo "--no-dev"; fi`
+
+FROM phpswoole/swoole:4.8.7-php8.1-alpine as final
+RUN ["apk", "add", "docker"]
+
+ENV _APP_REDIS_HOST=redis \
+ _APP_REDIS_PORT=6379
+
+RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
+
+WORKDIR /usr/src/code
+
+COPY --from=composer /usr/local/src/vendor /usr/src/code/vendor
+
+# Add Source Code
+COPY ./src /usr/src/code/src
+COPY ./app /usr/src/code/app
+
+EXPOSE 80
+
+CMD ["php", "app/http.php"]
diff --git a/mock-server/app/http.php b/mock-server/app/http.php
new file mode 100644
index 000000000..848fb56f5
--- /dev/null
+++ b/mock-server/app/http.php
@@ -0,0 +1,782 @@
+set([
+ 'worker_num' => $workerNumber,
+ 'open_http2_protocol' => true,
+ // 'document_root' => __DIR__.'/../public',
+ // 'enable_static_handler' => true,
+ 'http_compression' => true,
+ 'http_compression_level' => 6,
+ 'package_max_length' => $payloadSize,
+ 'buffer_output_size' => $payloadSize,
+ ]);
+
+// Mock Routes
+App::get('/v1/mock/tests/foo')
+ ->desc('Get Foo')
+ ->groups(['mock'])
+ ->label('scope', 'public')
+ ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
+ ->label('sdk.namespace', 'foo')
+ ->label('sdk.method', 'get')
+ ->label('sdk.description', 'Mock a get request.')
+ ->label('sdk.response.code', Response::STATUS_CODE_OK)
+ ->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
+ ->label('sdk.response.model', Response::MODEL_MOCK)
+ ->label('sdk.mock', true)
+ ->param('x', '', new Text(100), 'Sample string param')
+ ->param('y', '', new Integer(true), 'Sample numeric param')
+ ->param('z', null, new ArrayList(new Text(256), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Sample array param')
+ ->action(function ($x, $y, $z) {
+ });
+
+App::post('/v1/mock/tests/foo')
+ ->desc('Post Foo')
+ ->groups(['mock'])
+ ->label('scope', 'public')
+ ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
+ ->label('sdk.namespace', 'foo')
+ ->label('sdk.method', 'post')
+ ->label('sdk.description', 'Mock a post request.')
+ ->label('sdk.response.code', Response::STATUS_CODE_OK)
+ ->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
+ ->label('sdk.response.model', Response::MODEL_MOCK)
+ ->label('sdk.mock', true)
+ ->param('x', '', new Text(100), 'Sample string param')
+ ->param('y', '', new Integer(true), 'Sample numeric param')
+ ->param('z', null, new ArrayList(new Text(256), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Sample array param')
+ ->action(function ($x, $y, $z) {
+ });
+
+App::patch('/v1/mock/tests/foo')
+ ->desc('Patch Foo')
+ ->groups(['mock'])
+ ->label('scope', 'public')
+ ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
+ ->label('sdk.namespace', 'foo')
+ ->label('sdk.method', 'patch')
+ ->label('sdk.description', 'Mock a patch request.')
+ ->label('sdk.response.code', Response::STATUS_CODE_OK)
+ ->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
+ ->label('sdk.response.model', Response::MODEL_MOCK)
+ ->label('sdk.mock', true)
+ ->param('x', '', new Text(100), 'Sample string param')
+ ->param('y', '', new Integer(true), 'Sample numeric param')
+ ->param('z', null, new ArrayList(new Text(256), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Sample array param')
+ ->action(function ($x, $y, $z) {
+ });
+
+App::put('/v1/mock/tests/foo')
+ ->desc('Put Foo')
+ ->groups(['mock'])
+ ->label('scope', 'public')
+ ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
+ ->label('sdk.namespace', 'foo')
+ ->label('sdk.method', 'put')
+ ->label('sdk.description', 'Mock a put request.')
+ ->label('sdk.response.code', Response::STATUS_CODE_OK)
+ ->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
+ ->label('sdk.response.model', Response::MODEL_MOCK)
+ ->label('sdk.mock', true)
+ ->param('x', '', new Text(100), 'Sample string param')
+ ->param('y', '', new Integer(true), 'Sample numeric param')
+ ->param('z', null, new ArrayList(new Text(256), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Sample array param')
+ ->action(function ($x, $y, $z) {
+ });
+
+App::delete('/v1/mock/tests/foo')
+ ->desc('Delete Foo')
+ ->groups(['mock'])
+ ->label('scope', 'public')
+ ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
+ ->label('sdk.namespace', 'foo')
+ ->label('sdk.method', 'delete')
+ ->label('sdk.description', 'Mock a delete request.')
+ ->label('sdk.response.code', Response::STATUS_CODE_OK)
+ ->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
+ ->label('sdk.response.model', Response::MODEL_MOCK)
+ ->label('sdk.mock', true)
+ ->param('x', '', new Text(100), 'Sample string param')
+ ->param('y', '', new Integer(true), 'Sample numeric param')
+ ->param('z', null, new ArrayList(new Text(256), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Sample array param')
+ ->action(function ($x, $y, $z) {
+ });
+
+App::get('/v1/mock/tests/bar')
+ ->desc('Get Bar')
+ ->groups(['mock'])
+ ->label('scope', 'public')
+ ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
+ ->label('sdk.namespace', 'bar')
+ ->label('sdk.method', 'get')
+ ->label('sdk.description', 'Mock a get request.')
+ ->label('sdk.response.code', Response::STATUS_CODE_OK)
+ ->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
+ ->label('sdk.response.model', Response::MODEL_MOCK)
+ ->label('sdk.mock', true)
+ ->param('required', '', new Text(100), 'Sample string param')
+ ->param('default', '', new Integer(true), 'Sample numeric param')
+ ->param('z', null, new ArrayList(new Text(256), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Sample array param')
+ ->action(function ($required, $default, $z) {
+ });
+
+App::post('/v1/mock/tests/bar')
+ ->desc('Post Bar')
+ ->groups(['mock'])
+ ->label('scope', 'public')
+ ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
+ ->label('sdk.namespace', 'bar')
+ ->label('sdk.method', 'post')
+ ->label('sdk.description', 'Mock a post request.')
+ ->label('sdk.response.code', Response::STATUS_CODE_OK)
+ ->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
+ ->label('sdk.response.model', Response::MODEL_MOCK)
+ ->label('sdk.offline.model', '/mock/tests/bar')
+ ->label('sdk.offline.key', '{required}')
+ ->label('sdk.mock', true)
+ ->param('required', '', new Text(100), 'Sample string param')
+ ->param('default', '', new Integer(true), 'Sample numeric param')
+ ->param('z', null, new ArrayList(new Text(256), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Sample array param')
+ ->action(function ($required, $default, $z) {
+ });
+
+App::patch('/v1/mock/tests/bar')
+ ->desc('Patch Bar')
+ ->groups(['mock'])
+ ->label('scope', 'public')
+ ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
+ ->label('sdk.namespace', 'bar')
+ ->label('sdk.method', 'patch')
+ ->label('sdk.description', 'Mock a patch request.')
+ ->label('sdk.response.code', Response::STATUS_CODE_OK)
+ ->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
+ ->label('sdk.response.model', Response::MODEL_MOCK)
+ ->label('sdk.mock', true)
+ ->param('required', '', new Text(100), 'Sample string param')
+ ->param('default', '', new Integer(true), 'Sample numeric param')
+ ->param('z', null, new ArrayList(new Text(256), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Sample array param')
+ ->action(function ($required, $default, $z) {
+ });
+
+App::put('/v1/mock/tests/bar')
+ ->desc('Put Bar')
+ ->groups(['mock'])
+ ->label('scope', 'public')
+ ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
+ ->label('sdk.namespace', 'bar')
+ ->label('sdk.method', 'put')
+ ->label('sdk.description', 'Mock a put request.')
+ ->label('sdk.response.code', Response::STATUS_CODE_OK)
+ ->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
+ ->label('sdk.response.model', Response::MODEL_MOCK)
+ ->label('sdk.mock', true)
+ ->param('required', '', new Text(100), 'Sample string param')
+ ->param('default', '', new Integer(true), 'Sample numeric param')
+ ->param('z', null, new ArrayList(new Text(256), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Sample array param')
+ ->action(function ($required, $default, $z) {
+ });
+
+App::delete('/v1/mock/tests/bar')
+ ->desc('Delete Bar')
+ ->groups(['mock'])
+ ->label('scope', 'public')
+ ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
+ ->label('sdk.namespace', 'bar')
+ ->label('sdk.method', 'delete')
+ ->label('sdk.description', 'Mock a delete request.')
+ ->label('sdk.response.code', Response::STATUS_CODE_OK)
+ ->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
+ ->label('sdk.response.model', Response::MODEL_MOCK)
+ ->label('sdk.mock', true)
+ ->param('required', '', new Text(100), 'Sample string param')
+ ->param('default', '', new Integer(true), 'Sample numeric param')
+ ->param('z', null, new ArrayList(new Text(256), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Sample array param')
+ ->action(function ($required, $default, $z) {
+ });
+
+App::get('/v1/mock/tests/general/headers')
+ ->desc('Get headers')
+ ->groups(['mock'])
+ ->label('scope', 'public')
+ ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
+ ->label('sdk.namespace', 'general')
+ ->label('sdk.method', 'headers')
+ ->label('sdk.description', 'Return headers from the request')
+ ->label('sdk.response.code', Response::STATUS_CODE_OK)
+ ->label('sdk.response.model', Response::MODEL_MOCK)
+ ->label('sdk.mock', true)
+ ->inject('request')
+ ->inject('response')
+ ->action(function (Request $request, UtopiaSwooleResponse $response) {
+ $res = [
+ 'x-sdk-name' => $request->getHeader('x-sdk-name'),
+ 'x-sdk-platform' => $request->getHeader('x-sdk-platform'),
+ 'x-sdk-language' => $request->getHeader('x-sdk-language'),
+ 'x-sdk-version' => $request->getHeader('x-sdk-version'),
+ ];
+ $res = array_map(function ($key, $value) {
+ return $key . ': ' . $value;
+ }, array_keys($res), $res);
+ $res = implode("; ", $res);
+
+ $response->json(['result' => $res]);
+ });
+
+App::get('/v1/mock/tests/general/download')
+ ->desc('Download File')
+ ->groups(['mock'])
+ ->label('scope', 'public')
+ ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
+ ->label('sdk.namespace', 'general')
+ ->label('sdk.method', 'download')
+ ->label('sdk.methodType', 'location')
+ ->label('sdk.description', 'Mock a file download request.')
+ ->label('sdk.response.type', '*/*')
+ ->label('sdk.response.code', Response::STATUS_CODE_OK)
+ ->label('sdk.mock', true)
+ ->inject('response')
+ ->action(function (UtopiaSwooleResponse $response) {
+
+ $response
+ ->setContentType('text/plain')
+ ->addHeader('Content-Disposition', 'attachment; filename="test.txt"')
+ ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache
+ ->addHeader('X-Peak', \memory_get_peak_usage())
+ ->send("GET:/v1/mock/tests/general/download:passed");
+ });
+
+App::post('/v1/mock/tests/general/upload')
+ ->desc('Upload File')
+ ->groups(['mock'])
+ ->label('scope', 'public')
+ ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
+ ->label('sdk.namespace', 'general')
+ ->label('sdk.method', 'upload')
+ ->label('sdk.description', 'Mock a file upload request.')
+ ->label('sdk.request.type', 'multipart/form-data')
+ ->label('sdk.response.code', Response::STATUS_CODE_OK)
+ ->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
+ ->label('sdk.response.model', Response::MODEL_MOCK)
+ ->label('sdk.mock', true)
+ ->param('x', '', new Text(100), 'Sample string param')
+ ->param('y', '', new Integer(true), 'Sample numeric param')
+ ->param('z', null, new ArrayList(new Text(256), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Sample array param')
+ ->param('file', [], new File(), 'Sample file param', skipValidation: true)
+ ->inject('request')
+ ->inject('response')
+ ->action(function (string $x, int $y, array $z, mixed $file, Request $request, UtopiaSwooleResponse $response) {
+
+ $file = $request->getFiles('file');
+
+ $contentRange = $request->getHeader('content-range');
+
+ $chunkSize = 5 * 1024 * 1024; // 5MB
+
+ if (!empty($contentRange)) {
+ $start = $request->getContentRangeStart();
+ $end = $request->getContentRangeEnd();
+ $size = $request->getContentRangeSize();
+ $id = $request->getHeader('x-appwrite-id', '');
+ $file['size'] = (\is_array($file['size'])) ? $file['size'][0] : $file['size'];
+
+ if (is_null($start) || is_null($end) || is_null($size) || $end >= $size) {
+ throw new Exception(Exception::GENERAL_MOCK, 'Invalid content-range header');
+ }
+
+ if ($start > $end || $end > $size) {
+ throw new Exception(Exception::GENERAL_MOCK, 'Invalid content-range header');
+ }
+
+ if ($start === 0 && !empty($id)) {
+ throw new Exception(Exception::GENERAL_MOCK, 'First chunked request cannot have id header');
+ }
+
+ if ($start !== 0 && $id !== 'newfileid') {
+ throw new Exception(Exception::GENERAL_MOCK, 'All chunked request must have id header (except first)');
+ }
+
+ if ($end !== $size - 1 && $end - $start + 1 !== $chunkSize) {
+ throw new Exception(Exception::GENERAL_MOCK, 'Chunk size must be 5MB (except last chunk)');
+ }
+
+ if ($end !== $size - 1 && $file['size'] !== $chunkSize) {
+ throw new Exception(Exception::GENERAL_MOCK, 'Wrong chunk size');
+ }
+
+ if ($file['size'] > $chunkSize) {
+ throw new Exception(Exception::GENERAL_MOCK, 'Chunk size must be 5MB or less');
+ }
+
+ if ($end !== $size - 1) {
+ $response->json([
+ '$id' => ID::custom('newfileid'),
+ 'chunksTotal' => (int) ceil($size / ($end + 1 - $start)),
+ 'chunksUploaded' => ceil($start / $chunkSize) + 1
+ ]);
+ }
+ } else {
+ $file['tmp_name'] = (\is_array($file['tmp_name'])) ? $file['tmp_name'][0] : $file['tmp_name'];
+ $file['name'] = (\is_array($file['name'])) ? $file['name'][0] : $file['name'];
+ $file['size'] = (\is_array($file['size'])) ? $file['size'][0] : $file['size'];
+
+ if ($file['name'] !== 'file.png') {
+ throw new Exception(Exception::GENERAL_MOCK, 'Wrong file name');
+ }
+
+ if ($file['size'] !== 38756) {
+ throw new Exception(Exception::GENERAL_MOCK, 'Wrong file size');
+ }
+
+ if (\md5(\file_get_contents($file['tmp_name'])) !== 'd80e7e6999a3eb2ae0d631a96fe135a4') {
+ throw new Exception(Exception::GENERAL_MOCK, 'Wrong file uploaded');
+ }
+ }
+ });
+
+App::get('/v1/mock/tests/general/redirect')
+ ->desc('Redirect')
+ ->groups(['mock'])
+ ->label('scope', 'public')
+ ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
+ ->label('sdk.namespace', 'general')
+ ->label('sdk.method', 'redirect')
+ ->label('sdk.description', 'Mock a redirect request.')
+ ->label('sdk.response.code', Response::STATUS_CODE_MOVED_PERMANENTLY)
+ ->label('sdk.response.type', Response::CONTENT_TYPE_HTML)
+ ->label('sdk.response.model', Response::MODEL_MOCK)
+ ->label('sdk.mock', true)
+ ->inject('response')
+ ->action(function (UtopiaSwooleResponse $response) {
+
+ $response->redirect('/v1/mock/tests/general/redirect/done');
+ });
+
+App::get('/v1/mock/tests/general/redirect/done')
+ ->desc('Redirected')
+ ->groups(['mock'])
+ ->label('scope', 'public')
+ ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
+ ->label('sdk.namespace', 'general')
+ ->label('sdk.method', 'redirected')
+ ->label('sdk.description', 'Mock a redirected request.')
+ ->label('sdk.response.code', Response::STATUS_CODE_OK)
+ ->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
+ ->label('sdk.response.model', Response::MODEL_MOCK)
+ ->label('sdk.mock', true)
+ ->action(function () {
+ });
+
+App::get('/v1/mock/tests/general/set-cookie')
+ ->desc('Set Cookie')
+ ->groups(['mock'])
+ ->label('scope', 'public')
+ ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
+ ->label('sdk.namespace', 'general')
+ ->label('sdk.method', 'setCookie')
+ ->label('sdk.description', 'Mock a set cookie request.')
+ ->label('sdk.response.code', Response::STATUS_CODE_OK)
+ ->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
+ ->label('sdk.response.model', Response::MODEL_MOCK)
+ ->label('sdk.mock', true)
+ ->inject('response')
+ ->inject('request')
+ ->action(function (UtopiaSwooleResponse $response, Request $request) {
+
+ $response->addCookie('cookieName', 'cookieValue', \time() + 31536000, '/', $request->getHostname(), true, true);
+ });
+
+App::get('/v1/mock/tests/general/get-cookie')
+ ->desc('Get Cookie')
+ ->groups(['mock'])
+ ->label('scope', 'public')
+ ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
+ ->label('sdk.namespace', 'general')
+ ->label('sdk.method', 'getCookie')
+ ->label('sdk.description', 'Mock a cookie response.')
+ ->label('sdk.response.code', Response::STATUS_CODE_OK)
+ ->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
+ ->label('sdk.response.model', Response::MODEL_MOCK)
+ ->label('sdk.mock', true)
+ ->inject('request')
+ ->action(function (Request $request) {
+
+ if ($request->getCookie('cookieName', '') !== 'cookieValue') {
+ throw new Exception(Exception::GENERAL_MOCK, 'Missing cookie value');
+ }
+ });
+
+App::get('/v1/mock/tests/general/empty')
+ ->desc('Empty Response')
+ ->groups(['mock'])
+ ->label('scope', 'public')
+ ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
+ ->label('sdk.namespace', 'general')
+ ->label('sdk.method', 'empty')
+ ->label('sdk.description', 'Mock an empty response.')
+ ->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
+ ->label('sdk.response.model', Response::MODEL_NONE)
+ ->label('sdk.mock', true)
+ ->inject('response')
+ ->action(function (UtopiaSwooleResponse $response) {
+
+ $response->noContent();
+ });
+
+App::post('/v1/mock/tests/general/nullable')
+ ->desc('Nullable Test')
+ ->groups(['mock'])
+ ->label('scope', 'public')
+ ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
+ ->label('sdk.namespace', 'general')
+ ->label('sdk.method', 'nullable')
+ ->label('sdk.description', 'Mock a nullable parameter.')
+ ->label('sdk.mock', true)
+ ->param('required', '', new Text(100), 'Sample string param')
+ ->param('nullable', '', new Nullable(new Text(100)), 'Sample string param')
+ ->param('optional', '', new Text(100), 'Sample string param', true)
+ ->action(function (string $required, string $nullable, ?string $optional) {
+ });
+
+App::post('/v1/mock/tests/general/enum')
+ ->desc('Enum Test')
+ ->groups(['mock'])
+ ->label('scope', 'public')
+ ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
+ ->label('sdk.namespace', 'general')
+ ->label('sdk.method', 'enum')
+ ->label('sdk.description', 'Mock an enum parameter.')
+ ->label('sdk.mock', true)
+ ->param('mockType', '', new WhiteList(['first', 'second', 'third']), 'Sample enum param')
+ ->action(function (string $mockType) {
+ });
+
+App::get('/v1/mock/tests/general/400-error')
+ ->desc('400 Error')
+ ->groups(['mock'])
+ ->label('scope', 'public')
+ ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
+ ->label('sdk.namespace', 'general')
+ ->label('sdk.method', 'error400')
+ ->label('sdk.description', 'Mock a 400 failed request.')
+ ->label('sdk.response.code', Response::STATUS_CODE_BAD_REQUEST)
+ ->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
+ ->label('sdk.response.model', Response::MODEL_ERROR)
+ ->label('sdk.mock', true)
+ ->action(function () {
+ throw new Exception(Exception::GENERAL_MOCK, 'Mock 400 error');
+ });
+
+App::get('/v1/mock/tests/general/500-error')
+ ->desc('500 Error')
+ ->groups(['mock'])
+ ->label('scope', 'public')
+ ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
+ ->label('sdk.namespace', 'general')
+ ->label('sdk.method', 'error500')
+ ->label('sdk.description', 'Mock a 500 failed request.')
+ ->label('sdk.response.code', Response::STATUS_CODE_INTERNAL_SERVER_ERROR)
+ ->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
+ ->label('sdk.response.model', Response::MODEL_ERROR)
+ ->label('sdk.mock', true)
+ ->action(function () {
+ throw new Exception(Exception::GENERAL_MOCK, 'Mock 500 error', 500);
+ });
+
+App::get('/v1/mock/tests/general/502-error')
+ ->desc('502 Error')
+ ->groups(['mock'])
+ ->label('scope', 'public')
+ ->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
+ ->label('sdk.namespace', 'general')
+ ->label('sdk.method', 'error502')
+ ->label('sdk.description', 'Mock a 502 bad gateway.')
+ ->label('sdk.response.code', Response::STATUS_CODE_BAD_GATEWAY)
+ ->label('sdk.response.type', Response::CONTENT_TYPE_TEXT)
+ ->label('sdk.response.model', Response::MODEL_ANY)
+ ->label('sdk.mock', true)
+ ->inject('response')
+ ->action(function (UtopiaSwooleResponse $response) {
+
+ $response
+ ->setStatusCode(502)
+ ->text('This is a text error');
+ });
+
+App::get('/v1/mock/tests/general/oauth2')
+ ->desc('OAuth Login')
+ ->groups(['mock'])
+ ->label('scope', 'public')
+ ->label('docs', false)
+ ->label('sdk.mock', true)
+ ->param('client_id', '', new Text(100), 'OAuth2 Client ID.')
+ ->param('redirect_uri', '', new Host(['localhost']), 'OAuth2 Redirect URI.') // Important to deny an open redirect attack
+ ->param('scope', '', new Text(100), 'OAuth2 scope list.')
+ ->param('state', '', new Text(1024), 'OAuth2 state.')
+ ->inject('response')
+ ->action(function (string $client_id, string $redirectURI, string $scope, string $state, UtopiaSwooleResponse $response) {
+
+ $response->redirect($redirectURI . '?' . \http_build_query(['code' => 'abcdef', 'state' => $state]));
+ });
+
+App::get('/v1/mock/tests/general/oauth2/token')
+ ->desc('OAuth2 Token')
+ ->groups(['mock'])
+ ->label('scope', 'public')
+ ->label('docs', false)
+ ->label('sdk.mock', true)
+ ->param('client_id', '', new Text(100), 'OAuth2 Client ID.')
+ ->param('client_secret', '', new Text(100), 'OAuth2 scope list.')
+ ->param('grant_type', 'authorization_code', new WhiteList(['refresh_token', 'authorization_code']), 'OAuth2 Grant Type.', true)
+ ->param('redirect_uri', '', new Host(['localhost']), 'OAuth2 Redirect URI.', true)
+ ->param('code', '', new Text(100), 'OAuth2 state.', true)
+ ->param('refresh_token', '', new Text(100), 'OAuth2 refresh token.', true)
+ ->inject('response')
+ ->action(function (string $client_id, string $client_secret, string $grantType, string $redirectURI, string $code, string $refreshToken, UtopiaSwooleResponse $response) {
+
+ if ($client_id != '1') {
+ throw new Exception(Exception::GENERAL_MOCK, 'Invalid client ID');
+ }
+
+ if ($client_secret != '123456') {
+ throw new Exception(Exception::GENERAL_MOCK, 'Invalid client secret');
+ }
+
+ $responseJson = [
+ 'access_token' => '123456',
+ 'refresh_token' => 'tuvwxyz',
+ 'expires_in' => 14400
+ ];
+
+ if ($grantType === 'authorization_code') {
+ if ($code !== 'abcdef') {
+ throw new Exception(Exception::GENERAL_MOCK, 'Invalid token');
+ }
+
+ $response->json($responseJson);
+ } elseif ($grantType === 'refresh_token') {
+ if ($refreshToken !== 'tuvwxyz') {
+ throw new Exception(Exception::GENERAL_MOCK, 'Invalid refresh token');
+ }
+
+ $response->json($responseJson);
+ } else {
+ throw new Exception(Exception::GENERAL_MOCK, 'Invalid grant type');
+ }
+ });
+
+App::get('/v1/mock/tests/general/oauth2/user')
+ ->desc('OAuth2 User')
+ ->groups(['mock'])
+ ->label('scope', 'public')
+ ->label('docs', false)
+ ->param('token', '', new Text(100), 'OAuth2 Access Token.')
+ ->inject('response')
+ ->action(function (string $token, UtopiaSwooleResponse $response) {
+
+ if ($token != '123456') {
+ throw new Exception(Exception::GENERAL_MOCK, 'Invalid token');
+ }
+
+ $response->json([
+ 'id' => 1,
+ 'name' => 'User Name',
+ 'email' => 'useroauth@localhost.test',
+ ]);
+ });
+
+App::get('/v1/mock/tests/general/oauth2/success')
+ ->desc('OAuth2 Success')
+ ->groups(['mock'])
+ ->label('scope', 'public')
+ ->label('docs', false)
+ ->inject('response')
+ ->action(function (UtopiaSwooleResponse $response) {
+
+ $response->json([
+ 'result' => 'success',
+ ]);
+ });
+
+App::get('/v1/mock/tests/general/oauth2/failure')
+ ->desc('OAuth2 Failure')
+ ->groups(['mock'])
+ ->label('scope', 'public')
+ ->label('docs', false)
+ ->inject('response')
+ ->action(function (UtopiaSwooleResponse $response) {
+
+ $response
+ ->setStatusCode(Response::STATUS_CODE_BAD_REQUEST)
+ ->json([
+ 'result' => 'failure',
+ ]);
+ });
+
+App::shutdown()
+ ->groups(['mock'])
+ ->inject('utopia')
+ ->inject('response')
+ ->inject('request')
+ ->action(function (App $utopia, UtopiaSwooleResponse $response, Request $request) {
+
+ $result = [];
+ $route = $utopia->getRoute();
+ $path = APP_STORAGE_CACHE . '/tests.json';
+ $tests = (\file_exists($path)) ? \json_decode(\file_get_contents($path), true) : [];
+
+ if (!\is_array($tests)) {
+ throw new Exception(Exception::GENERAL_MOCK, 'Failed to read results', 500);
+ }
+
+ $result[$route->getMethod() . ':' . $route->getPath()] = true;
+
+ $tests = \array_merge($tests, $result);
+
+ if (!\file_put_contents($path, \json_encode($tests), LOCK_EX)) {
+ throw new Exception(Exception::GENERAL_MOCK, 'Failed to save results', 500);
+ }
+
+ $response->json(['result' => $route->getMethod() . ':' . $route->getPath() . ':passed']);
+ });
+
+App::error()
+ ->inject('utopia')
+ ->inject('error')
+ ->inject('request')
+ ->inject('response')
+ ->action(
+ function ($utopia, $error, $request, $response) {
+ $route = $utopia->match($request);
+
+ $code = $error->getCode();
+ $message = $error->getMessage();
+ $file = $error->getFile();
+ $line = $error->getLine();
+ $trace = $error->getTrace();
+
+ if (php_sapi_name() === 'cli') {
+ Console::error('[Error] Timestamp: ' . date('c', time()));
+
+ if ($route) {
+ Console::error('[Error] Method: ' . $route->getMethod());
+ Console::error('[Error] URL: ' . $route->getPath());
+ }
+
+ Console::error('[Error] Type: ' . get_class($error));
+ Console::error('[Error] Message: ' . $message);
+ Console::error('[Error] File: ' . $file);
+ Console::error('[Error] Line: ' . $line);
+ }
+
+ switch ($code) {
+ case 400: // Error allowed publicly
+ case 401: // Error allowed publicly
+ case 402: // Error allowed publicly
+ case 403: // Error allowed publicly
+ case 404: // Error allowed publicly
+ case 406: // Error allowed publicly
+ case 409: // Error allowed publicly
+ case 412: // Error allowed publicly
+ case 425: // Error allowed publicly
+ case 429: // Error allowed publicly
+ case 501: // Error allowed publicly
+ case 503: // Error allowed publicly
+ $code = $error->getCode();
+ break;
+ default:
+ $code = 500; // All other errors get the generic 500 server error status code
+ }
+
+ $output = ((App::isDevelopment())) ? [
+ 'message' => $message,
+ 'code' => $code,
+ 'file' => $file,
+ 'line' => $line,
+ 'trace' => $trace,
+ ] : [
+ 'message' => $message,
+ 'code' => $code,
+ ];
+
+ $response
+ ->addHeader('Cache-Control', 'no-cache, no-store, must-revalidate')
+ ->addHeader('Expires', '0')
+ ->addHeader('Pragma', 'no-cache')
+ ->setStatusCode($code);
+
+ $response->json($output);
+ },
+ ['utopia', 'error', 'request', 'response']
+ );
+
+$http->on(
+ 'start',
+ function (Server $http) use ($payloadSize) {
+ Console::success('Server started successfully (max payload is ' . number_format($payloadSize) . ' bytes)');
+ Console::info("Master pid {$http->master_pid}, manager pid {$http->manager_pid}");
+ // listen ctrl + c
+ Process::signal(
+ 2,
+ function () use ($http) {
+ Console::log('Stop by Ctrl+C');
+ $http->shutdown();
+ }
+ );
+ }
+);
+
+$http->on(
+ 'request',
+ function (SwooleRequest $swooleRequest, SwooleResponse $swooleResponse) {
+ $request = new Request($swooleRequest);
+ $response = new UtopiaSwooleResponse($swooleResponse);
+
+ $app = new App('UTC');
+
+ $app->run($request, $response);
+ }
+);
+
+$http->start();
diff --git a/mock-server/composer.json b/mock-server/composer.json
new file mode 100644
index 000000000..faf40c69f
--- /dev/null
+++ b/mock-server/composer.json
@@ -0,0 +1,24 @@
+{
+ "name": "utopia/mock-server",
+ "description": "Mock server for appwrite/sdk-generator",
+ "autoload": {
+ "psr-4": {
+ "Utopia\\MockServer\\": "src/"
+ }
+ },
+ "authors": [
+ {
+ "name": "Bradley Schofield",
+ "email": "bradley@appwrite.io"
+ }
+ ],
+ "require": {
+ "utopia-php/framework": "^0.31.0",
+ "utopia-php/database": "^0.44.2",
+ "utopia-php/cli": "^0.16.0",
+ "utopia-php/swoole": "^0.5.0"
+ },
+ "require-dev": {
+ "swoole/ide-helper": "^5.1"
+ }
+}
diff --git a/mock-server/composer.lock b/mock-server/composer.lock
new file mode 100644
index 000000000..45311434f
--- /dev/null
+++ b/mock-server/composer.lock
@@ -0,0 +1,573 @@
+{
+ "_readme": [
+ "This file locks the dependencies of your project to a known state",
+ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
+ "This file is @generated automatically"
+ ],
+ "content-hash": "64978d2ed8d7602b1a2430086a54fbbd",
+ "packages": [
+ {
+ "name": "jean85/pretty-package-versions",
+ "version": "2.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Jean85/pretty-package-versions.git",
+ "reference": "ae547e455a3d8babd07b96966b17d7fd21d9c6af"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/ae547e455a3d8babd07b96966b17d7fd21d9c6af",
+ "reference": "ae547e455a3d8babd07b96966b17d7fd21d9c6af",
+ "shasum": ""
+ },
+ "require": {
+ "composer-runtime-api": "^2.0.0",
+ "php": "^7.1|^8.0"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "^2.17",
+ "jean85/composer-provided-replaced-stub-package": "^1.0",
+ "phpstan/phpstan": "^0.12.66",
+ "phpunit/phpunit": "^7.5|^8.5|^9.4",
+ "vimeo/psalm": "^4.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Jean85\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Alessandro Lai",
+ "email": "alessandro.lai85@gmail.com"
+ }
+ ],
+ "description": "A library to get pretty versions strings of installed dependencies",
+ "keywords": [
+ "composer",
+ "package",
+ "release",
+ "versions"
+ ],
+ "support": {
+ "issues": "https://github.com/Jean85/pretty-package-versions/issues",
+ "source": "https://github.com/Jean85/pretty-package-versions/tree/2.0.5"
+ },
+ "time": "2021-10-08T21:21:46+00:00"
+ },
+ {
+ "name": "mongodb/mongodb",
+ "version": "1.10.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/mongodb/mongo-php-library.git",
+ "reference": "b0bbd657f84219212487d01a8ffe93a789e1e488"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/mongodb/mongo-php-library/zipball/b0bbd657f84219212487d01a8ffe93a789e1e488",
+ "reference": "b0bbd657f84219212487d01a8ffe93a789e1e488",
+ "shasum": ""
+ },
+ "require": {
+ "ext-hash": "*",
+ "ext-json": "*",
+ "ext-mongodb": "^1.11.0",
+ "jean85/pretty-package-versions": "^1.2 || ^2.0.1",
+ "php": "^7.1 || ^8.0",
+ "symfony/polyfill-php80": "^1.19"
+ },
+ "require-dev": {
+ "doctrine/coding-standard": "^9.0",
+ "squizlabs/php_codesniffer": "^3.6",
+ "symfony/phpunit-bridge": "^5.2"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.10.x-dev"
+ }
+ },
+ "autoload": {
+ "files": [
+ "src/functions.php"
+ ],
+ "psr-4": {
+ "MongoDB\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "Apache-2.0"
+ ],
+ "authors": [
+ {
+ "name": "Andreas Braun",
+ "email": "andreas.braun@mongodb.com"
+ },
+ {
+ "name": "Jeremy Mikola",
+ "email": "jmikola@gmail.com"
+ }
+ ],
+ "description": "MongoDB driver library",
+ "homepage": "https://jira.mongodb.org/browse/PHPLIB",
+ "keywords": [
+ "database",
+ "driver",
+ "mongodb",
+ "persistence"
+ ],
+ "support": {
+ "issues": "https://github.com/mongodb/mongo-php-library/issues",
+ "source": "https://github.com/mongodb/mongo-php-library/tree/1.10.0"
+ },
+ "time": "2021-10-20T22:22:37+00:00"
+ },
+ {
+ "name": "symfony/polyfill-php80",
+ "version": "v1.28.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-php80.git",
+ "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/6caa57379c4aec19c0a12a38b59b26487dcfe4b5",
+ "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "1.28-dev"
+ },
+ "thanks": {
+ "name": "symfony/polyfill",
+ "url": "https://github.com/symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Php80\\": ""
+ },
+ "classmap": [
+ "Resources/stubs"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Ion Bazan",
+ "email": "ion.bazan@gmail.com"
+ },
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-php80/tree/v1.28.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2023-01-26T09:26:14+00:00"
+ },
+ {
+ "name": "utopia-php/cache",
+ "version": "0.8.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/utopia-php/cache.git",
+ "reference": "212e66100a1f32e674fca5d9bc317cc998303089"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/utopia-php/cache/zipball/212e66100a1f32e674fca5d9bc317cc998303089",
+ "reference": "212e66100a1f32e674fca5d9bc317cc998303089",
+ "shasum": ""
+ },
+ "require": {
+ "ext-json": "*",
+ "ext-memcached": "*",
+ "ext-redis": "*",
+ "php": ">=8.0"
+ },
+ "require-dev": {
+ "laravel/pint": "1.2.*",
+ "phpunit/phpunit": "^9.3",
+ "vimeo/psalm": "4.13.1"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Utopia\\Cache\\": "src/Cache"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "A simple cache library to manage application cache storing, loading and purging",
+ "keywords": [
+ "cache",
+ "framework",
+ "php",
+ "upf",
+ "utopia"
+ ],
+ "support": {
+ "issues": "https://github.com/utopia-php/cache/issues",
+ "source": "https://github.com/utopia-php/cache/tree/0.8.0"
+ },
+ "time": "2022-10-16T16:48:09+00:00"
+ },
+ {
+ "name": "utopia-php/cli",
+ "version": "0.16.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/utopia-php/cli.git",
+ "reference": "5b936638c90c86d1bae83d0dbe81fe14d12ff8ff"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/utopia-php/cli/zipball/5b936638c90c86d1bae83d0dbe81fe14d12ff8ff",
+ "reference": "5b936638c90c86d1bae83d0dbe81fe14d12ff8ff",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.4",
+ "utopia-php/framework": "0.*.*"
+ },
+ "require-dev": {
+ "laravel/pint": "1.2.*",
+ "phpunit/phpunit": "^9.3",
+ "squizlabs/php_codesniffer": "^3.6",
+ "vimeo/psalm": "4.0.1"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Utopia\\CLI\\": "src/CLI"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "A simple CLI library to manage command line applications",
+ "keywords": [
+ "cli",
+ "command line",
+ "framework",
+ "php",
+ "upf",
+ "utopia"
+ ],
+ "support": {
+ "issues": "https://github.com/utopia-php/cli/issues",
+ "source": "https://github.com/utopia-php/cli/tree/0.16.0"
+ },
+ "time": "2023-08-05T13:13:08+00:00"
+ },
+ {
+ "name": "utopia-php/database",
+ "version": "0.44.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/utopia-php/database.git",
+ "reference": "591cadbc2c622a3304aae9a16ebfdbe75ef33a06"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/utopia-php/database/zipball/591cadbc2c622a3304aae9a16ebfdbe75ef33a06",
+ "reference": "591cadbc2c622a3304aae9a16ebfdbe75ef33a06",
+ "shasum": ""
+ },
+ "require": {
+ "ext-mbstring": "*",
+ "ext-pdo": "*",
+ "php": ">=8.0",
+ "utopia-php/cache": "0.8.*",
+ "utopia-php/framework": "0.*.*",
+ "utopia-php/mongo": "0.3.*"
+ },
+ "require-dev": {
+ "fakerphp/faker": "^1.14",
+ "laravel/pint": "1.4.*",
+ "pcov/clobber": "^2.0",
+ "phpstan/phpstan": "1.10.*",
+ "phpunit/phpunit": "^9.4",
+ "rregeer/phpunit-coverage-check": "^0.3.1",
+ "swoole/ide-helper": "4.8.0",
+ "utopia-php/cli": "^0.14.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Utopia\\Database\\": "src/Database"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "A simple library to manage application persistence using multiple database adapters",
+ "keywords": [
+ "database",
+ "framework",
+ "php",
+ "upf",
+ "utopia"
+ ],
+ "support": {
+ "issues": "https://github.com/utopia-php/database/issues",
+ "source": "https://github.com/utopia-php/database/tree/0.44.2"
+ },
+ "time": "2023-10-19T07:39:00+00:00"
+ },
+ {
+ "name": "utopia-php/framework",
+ "version": "0.31.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/utopia-php/framework.git",
+ "reference": "207f77378965fca9a9bc3783ea379d3549f86bc0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/utopia-php/framework/zipball/207f77378965fca9a9bc3783ea379d3549f86bc0",
+ "reference": "207f77378965fca9a9bc3783ea379d3549f86bc0",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.0"
+ },
+ "require-dev": {
+ "laravel/pint": "^1.2",
+ "phpbench/phpbench": "^1.2",
+ "phpstan/phpstan": "^1.10",
+ "phpunit/phpunit": "^9.5.25"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Utopia\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "A simple, light and advanced PHP framework",
+ "keywords": [
+ "framework",
+ "php",
+ "upf"
+ ],
+ "support": {
+ "issues": "https://github.com/utopia-php/framework/issues",
+ "source": "https://github.com/utopia-php/framework/tree/0.31.0"
+ },
+ "time": "2023-08-30T16:10:04+00:00"
+ },
+ {
+ "name": "utopia-php/mongo",
+ "version": "0.3.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/utopia-php/mongo.git",
+ "reference": "52326a9a43e2d27ff0c15c48ba746dacbe9a7aee"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/utopia-php/mongo/zipball/52326a9a43e2d27ff0c15c48ba746dacbe9a7aee",
+ "reference": "52326a9a43e2d27ff0c15c48ba746dacbe9a7aee",
+ "shasum": ""
+ },
+ "require": {
+ "ext-mongodb": "*",
+ "mongodb/mongodb": "1.10.0",
+ "php": ">=8.0"
+ },
+ "require-dev": {
+ "fakerphp/faker": "^1.14",
+ "laravel/pint": "1.2.*",
+ "phpstan/phpstan": "1.8.*",
+ "phpunit/phpunit": "^9.4",
+ "swoole/ide-helper": "4.8.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Utopia\\Mongo\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Eldad Fux",
+ "email": "eldad@appwrite.io"
+ },
+ {
+ "name": "Wess",
+ "email": "wess@appwrite.io"
+ }
+ ],
+ "description": "A simple library to manage Mongo database",
+ "keywords": [
+ "database",
+ "mongo",
+ "php",
+ "upf",
+ "utopia"
+ ],
+ "support": {
+ "issues": "https://github.com/utopia-php/mongo/issues",
+ "source": "https://github.com/utopia-php/mongo/tree/0.3.1"
+ },
+ "time": "2023-09-01T17:25:28+00:00"
+ },
+ {
+ "name": "utopia-php/swoole",
+ "version": "0.5.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/utopia-php/swoole.git",
+ "reference": "c2a3a4f944a2f22945af3cbcb95b13f0769628b1"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/utopia-php/swoole/zipball/c2a3a4f944a2f22945af3cbcb95b13f0769628b1",
+ "reference": "c2a3a4f944a2f22945af3cbcb95b13f0769628b1",
+ "shasum": ""
+ },
+ "require": {
+ "ext-swoole": "*",
+ "php": ">=8.0",
+ "utopia-php/framework": "0.*.*"
+ },
+ "require-dev": {
+ "laravel/pint": "1.2.*",
+ "phpunit/phpunit": "^9.3",
+ "swoole/ide-helper": "4.8.3",
+ "vimeo/psalm": "4.15.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Utopia\\Swoole\\": "src/Swoole"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "An extension for Utopia Framework to work with PHP Swoole as a PHP FPM alternative",
+ "keywords": [
+ "framework",
+ "http",
+ "php",
+ "server",
+ "swoole",
+ "upf",
+ "utopia"
+ ],
+ "support": {
+ "issues": "https://github.com/utopia-php/swoole/issues",
+ "source": "https://github.com/utopia-php/swoole/tree/0.5.0"
+ },
+ "time": "2022-10-19T22:19:07+00:00"
+ }
+ ],
+ "packages-dev": [
+ {
+ "name": "swoole/ide-helper",
+ "version": "5.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/swoole/ide-helper.git",
+ "reference": "07692fa8f1bb8eac828410acd613ea5877237b09"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/swoole/ide-helper/zipball/07692fa8f1bb8eac828410acd613ea5877237b09",
+ "reference": "07692fa8f1bb8eac828410acd613ea5877237b09",
+ "shasum": ""
+ },
+ "type": "library",
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "Apache-2.0"
+ ],
+ "authors": [
+ {
+ "name": "Team Swoole",
+ "email": "team@swoole.com"
+ }
+ ],
+ "description": "IDE help files for Swoole.",
+ "support": {
+ "issues": "https://github.com/swoole/ide-helper/issues",
+ "source": "https://github.com/swoole/ide-helper/tree/5.1.0"
+ },
+ "time": "2023-10-05T04:52:59+00:00"
+ }
+ ],
+ "aliases": [],
+ "minimum-stability": "stable",
+ "stability-flags": [],
+ "prefer-stable": false,
+ "prefer-lowest": false,
+ "platform": [],
+ "platform-dev": [],
+ "plugin-api-version": "2.6.0"
+}
diff --git a/mock-server/docker-compose.yml b/mock-server/docker-compose.yml
new file mode 100644
index 000000000..35227488b
--- /dev/null
+++ b/mock-server/docker-compose.yml
@@ -0,0 +1,28 @@
+version: '3'
+
+services:
+ mockapi:
+ container_name: mockapi
+ build:
+ context: .
+ args:
+ DEBUG: "false"
+ TESTING: "true"
+ VERSION: "dev"
+ entrypoint:
+ - php
+ - -e
+ - app/http.php
+ volumes:
+ - ./app:/usr/src/code/app
+ - ./src:/usr/src/code/src
+ - mockapi-cache:/storage/cache
+ networks:
+ - mockapi
+
+networks:
+ mockapi:
+ name: mockapi
+
+volumes:
+ mockapi-cache:
\ No newline at end of file
diff --git a/mock-server/src/Utopia/Exception.php b/mock-server/src/Utopia/Exception.php
new file mode 100644
index 000000000..9a6d3b45d
--- /dev/null
+++ b/mock-server/src/Utopia/Exception.php
@@ -0,0 +1,190 @@
+ [
+ 'name' => Exception::GENERAL_UNKNOWN,
+ 'description' => 'An unknown error has occured. Please check the logs for more information.',
+ 'code' => 500,
+ ],
+ Exception::GENERAL_MOCK => [
+ 'name' => Exception::GENERAL_MOCK,
+ 'description' => 'General errors thrown by the mock controller used for testing.',
+ 'code' => 400,
+ ],
+ Exception::GENERAL_ACCESS_FORBIDDEN => [
+ 'name' => Exception::GENERAL_ACCESS_FORBIDDEN,
+ 'description' => 'Access to this API is forbidden.',
+ 'code' => 401,
+ ],
+ Exception::GENERAL_UNKNOWN_ORIGIN => [
+ 'name' => Exception::GENERAL_UNKNOWN_ORIGIN,
+ 'description' => 'The request originated from an unknown origin. If you trust this domain, please list it as a trusted platform in the Appwrite console.',
+ 'code' => 403,
+ ],
+ Exception::GENERAL_SERVICE_DISABLED => [
+ 'name' => Exception::GENERAL_SERVICE_DISABLED,
+ 'description' => 'The requested service is disabled. You can enable the service from the Appwrite console.',
+ 'code' => 503,
+ ],
+ Exception::GENERAL_UNAUTHORIZED_SCOPE => [
+ 'name' => Exception::GENERAL_UNAUTHORIZED_SCOPE,
+ 'description' => 'The current user or API key does not have the required scopes to access the requested resource.',
+ 'code' => 401,
+ ],
+ Exception::GENERAL_RATE_LIMIT_EXCEEDED => [
+ 'name' => Exception::GENERAL_RATE_LIMIT_EXCEEDED,
+ 'description' => 'Rate limit for the current endpoint has been exceeded. Please try again after some time.',
+ 'code' => 429,
+ ],
+ Exception::GENERAL_SMTP_DISABLED => [
+ 'name' => Exception::GENERAL_SMTP_DISABLED,
+ 'description' => 'SMTP is disabled on your Appwrite instance. You can learn more about setting up SMTP in our docs.',
+ 'code' => 503,
+ ],
+ Exception::GENERAL_PHONE_DISABLED => [
+ 'name' => Exception::GENERAL_PHONE_DISABLED,
+ 'description' => 'Phone provider is not configured. Please check the _APP_SMS_PROVIDER environment variable of your Appwrite server.',
+ 'code' => 503,
+ ],
+ Exception::GENERAL_ARGUMENT_INVALID => [
+ 'name' => Exception::GENERAL_ARGUMENT_INVALID,
+ 'description' => 'The request contains one or more invalid arguments. Please refer to the endpoint documentation.',
+ 'code' => 400,
+ ],
+ Exception::GENERAL_QUERY_LIMIT_EXCEEDED => [
+ 'name' => Exception::GENERAL_QUERY_LIMIT_EXCEEDED,
+ 'description' => 'Query limit exceeded for the current attribute. Usage of more than 100 query values on a single attribute is prohibited.',
+ 'code' => 400,
+ ],
+ Exception::GENERAL_QUERY_INVALID => [
+ 'name' => Exception::GENERAL_QUERY_INVALID,
+ 'description' => 'The query\'s syntax is invalid. Please check the query and try again.',
+ 'code' => 400,
+ ],
+ Exception::GENERAL_ROUTE_NOT_FOUND => [
+ 'name' => Exception::GENERAL_ROUTE_NOT_FOUND,
+ 'description' => 'The requested route was not found. Please refer to the API docs and try again.',
+ 'code' => 404,
+ ],
+ Exception::GENERAL_CURSOR_NOT_FOUND => [
+ 'name' => Exception::GENERAL_CURSOR_NOT_FOUND,
+ 'description' => 'The cursor is invalid. This can happen if the item represented by the cursor has been deleted.',
+ 'code' => 400,
+ ],
+ Exception::GENERAL_SERVER_ERROR => [
+ 'name' => Exception::GENERAL_SERVER_ERROR,
+ 'description' => 'An internal server error occurred.',
+ 'code' => 500,
+ ],
+ Exception::GENERAL_PROTOCOL_UNSUPPORTED => [
+ 'name' => Exception::GENERAL_PROTOCOL_UNSUPPORTED,
+ 'description' => 'The request cannot be fulfilled with the current protocol. Please check the value of the _APP_OPTIONS_FORCE_HTTPS environment variable.',
+ 'code' => 500,
+ ],
+ Exception::GENERAL_CODES_DISABLED => [
+ 'name' => Exception::GENERAL_CODES_DISABLED,
+ 'description' => 'Invitation codes are disabled on this server. Please contact the server administrator.',
+ 'code' => 500,
+ ],
+ Exception::GENERAL_USAGE_DISABLED => [
+ 'name' => Exception::GENERAL_USAGE_DISABLED,
+ 'description' => 'Usage stats is not configured. Please check the value of the _APP_USAGE_STATS environment variable of your Appwrite server.',
+ 'code' => 501,
+ ],
+ Exception::GENERAL_NOT_IMPLEMENTED => [
+ 'name' => Exception::GENERAL_NOT_IMPLEMENTED,
+ 'description' => 'This method was not fully implemented yet. If you believe this is a mistake, please upgrade your Appwrite server version.',
+ 'code' => 405,
+ ],
+
+ /** Functions */
+ Exception::FUNCTION_NOT_FOUND => [
+ 'name' => Exception::FUNCTION_NOT_FOUND,
+ 'description' => 'Function with the requested ID could not be found.',
+ 'code' => 404,
+ ],
+];
+
+class Exception extends \Exception
+{
+ /**
+ * Error Codes
+ *
+ * Naming the error types based on the following convention
+ * _
+ *
+ * Appwrite has the following entities:
+ * - General
+ */
+
+ /** General */
+ public const GENERAL_UNKNOWN = 'general_unknown';
+ public const GENERAL_MOCK = 'general_mock';
+ public const GENERAL_ACCESS_FORBIDDEN = 'general_access_forbidden';
+ public const GENERAL_UNKNOWN_ORIGIN = 'general_unknown_origin';
+ public const GENERAL_SERVICE_DISABLED = 'general_service_disabled';
+ public const GENERAL_UNAUTHORIZED_SCOPE = 'general_unauthorized_scope';
+ public const GENERAL_RATE_LIMIT_EXCEEDED = 'general_rate_limit_exceeded';
+ public const GENERAL_SMTP_DISABLED = 'general_smtp_disabled';
+ public const GENERAL_PHONE_DISABLED = 'general_phone_disabled';
+ public const GENERAL_ARGUMENT_INVALID = 'general_argument_invalid';
+ public const GENERAL_QUERY_LIMIT_EXCEEDED = 'general_query_limit_exceeded';
+ public const GENERAL_QUERY_INVALID = 'general_query_invalid';
+ public const GENERAL_ROUTE_NOT_FOUND = 'general_route_not_found';
+ public const GENERAL_CURSOR_NOT_FOUND = 'general_cursor_not_found';
+ public const GENERAL_SERVER_ERROR = 'general_server_error';
+ public const GENERAL_PROTOCOL_UNSUPPORTED = 'general_protocol_unsupported';
+ public const GENERAL_CODES_DISABLED = 'general_codes_disabled';
+ public const GENERAL_USAGE_DISABLED = 'general_usage_disabled';
+ public const GENERAL_NOT_IMPLEMENTED = 'general_not_implemented';
+
+ /** Functions */
+ public const FUNCTION_NOT_FOUND = 'function_not_found';
+
+ protected $type = '';
+ protected $errors = [];
+
+ public function __construct(string $type = Exception::GENERAL_UNKNOWN, string $message = null, int $code = null, \Throwable $previous = null)
+ {
+ $this->errors = errorConfig;
+ $this->type = $type;
+
+ if (isset($this->errors[$type])) {
+ $this->code = $this->errors[$type]['code'];
+ $this->message = $this->errors[$type]['description'];
+ }
+
+ $this->message = $message ?? $this->message;
+ $this->code = $code ?? $this->code;
+
+ parent::__construct($this->message, $this->code, $previous);
+ }
+
+ /**
+ * Get the type of the exception.
+ *
+ * @return string
+ */
+ public function getType(): string
+ {
+ return $this->type;
+ }
+
+ /**
+ * Set the type of the exception.
+ *
+ * @param string $type
+ *
+ * @return void
+ */
+ public function setType(string $type): void
+ {
+ $this->type = $type;
+ }
+}
diff --git a/mock-server/src/Utopia/File.php b/mock-server/src/Utopia/File.php
new file mode 100644
index 000000000..8eafd1925
--- /dev/null
+++ b/mock-server/src/Utopia/File.php
@@ -0,0 +1,50 @@
+rules;
+ }
+
+ /**
+ * Add a New Rule
+ * If rule is an array of documents with varying models
+ *
+ * @param string $key
+ * @param array $options
+ * @return Model
+ */
+ protected function addRule(string $key, array $options): self
+ {
+ $this->rules[$key] = array_merge([
+ 'required' => true,
+ 'array' => false,
+ 'description' => '',
+ 'example' => ''
+ ], $options);
+
+ return $this;
+ }
+
+ /**
+ * Delete an existing Rule
+ * If rule exists, it will be removed
+ *
+ * @param string $key
+ * @return Model
+ */
+ protected function removeRule(string $key): self
+ {
+ if (isset($this->rules[$key])) {
+ unset($this->rules[$key]);
+ }
+
+ return $this;
+ }
+
+ /**
+ * @return array
+ */
+ public function getRequired(): array
+ {
+ $list = [];
+
+ foreach ($this->rules as $key => $rule) {
+ if ($rule['required'] ?? false) {
+ $list[] = $key;
+ }
+ }
+
+ return $list;
+ }
+
+ /**
+ * Is None
+ *
+ * Use to check if response is empty
+ *
+ * @return bool
+ */
+ public function isNone(): bool
+ {
+ return $this->none;
+ }
+
+ /**
+ * Is Any
+ *
+ * Use to check if response is a wildcard
+ *
+ * @return bool
+ */
+ public function isAny(): bool
+ {
+ return $this->any;
+ }
+
+ /**
+ * Is Public
+ *
+ * Should this model be publicly available in docs and spec files?
+ *
+ * @return bool
+ */
+ public function isPublic(): bool
+ {
+ return $this->public;
+ }
+}
diff --git a/mock-server/src/Utopia/Response.php b/mock-server/src/Utopia/Response.php
new file mode 100644
index 000000000..756049364
--- /dev/null
+++ b/mock-server/src/Utopia/Response.php
@@ -0,0 +1,279 @@
+models[$instance->getType()] = $instance;
+
+ return $this;
+ }
+
+ /**
+ * Get Model Object
+ *
+ * @param string $key
+ * @return Model
+ * @throws Exception
+ */
+ public function getModel(string $key): Model
+ {
+ if (!isset($this->models[$key])) {
+ throw new \Exception('Undefined model: ' . $key);
+ }
+
+ return $this->models[$key];
+ }
+
+ /**
+ * Get Models List
+ *
+ * @return Model[]
+ */
+ public function getModels(): array
+ {
+ return $this->models;
+ }
+
+ /**
+ * Validate response objects and outputs
+ * the response according to given format type
+ *
+ * @param Document $document
+ * @param string $model
+ *
+ * return void
+ * @throws Exception
+ */
+ public function dynamic(Document $document, string $model): void
+ {
+ $output = $this->output(clone $document, $model);
+
+ // If filter is set, parse the output
+ if (self::hasFilter()) {
+ $output = self::getFilter()->parse($output, $model);
+ }
+
+ switch ($this->getContentType()) {
+ case self::CONTENT_TYPE_JSON:
+ $this->json(!empty($output) ? $output : new \stdClass());
+ break;
+
+ case self::CONTENT_TYPE_NULL:
+ break;
+
+ default:
+ if ($model === self::MODEL_NONE) {
+ $this->noContent();
+ } else {
+ $this->json(!empty($output) ? $output : new \stdClass());
+ }
+ break;
+ }
+ }
+
+ /**
+ * Generate valid response object from document data
+ *
+ * @param Document $document
+ * @param string $model
+ *
+ * return array
+ * @return array
+ * @throws Exception
+ */
+ public function output(Document $document, string $model): array
+ {
+ $data = clone $document;
+ $model = $this->getModel($model);
+ $output = [];
+
+ $data = $model->filter($data);
+
+ if ($model->isAny()) {
+ $this->payload = $data->getArrayCopy();
+
+ return $this->payload;
+ }
+
+ foreach ($model->getRules() as $key => $rule) {
+ if (!$data->isSet($key) && $rule['required']) { // do not set attribute in response if not required
+ if (\array_key_exists('default', $rule)) {
+ $data->setAttribute($key, $rule['default']);
+ } else {
+ throw new \Exception('Model ' . $model->getName() . ' is missing response key: ' . $key);
+ }
+ }
+
+ if ($rule['array']) {
+ if (!is_array($data[$key])) {
+ throw new \Exception($key . ' must be an array of type ' . $rule['type']);
+ }
+
+ foreach ($data[$key] as $index => $item) {
+ if ($item instanceof Document) {
+ if (\is_array($rule['type'])) {
+ foreach ($rule['type'] as $type) {
+ $condition = false;
+ foreach ($this->getModel($type)->conditions as $attribute => $val) {
+ $condition = $item->getAttribute($attribute) === $val;
+ if (!$condition) {
+ break;
+ }
+ }
+ if ($condition) {
+ $ruleType = $type;
+ break;
+ }
+ }
+ } else {
+ $ruleType = $rule['type'];
+ }
+
+ if (!array_key_exists($ruleType, $this->models)) {
+ throw new \Exception('Missing model for rule: ' . $ruleType);
+ }
+
+ $data[$key][$index] = $this->output($item, $ruleType);
+ }
+ }
+ } else {
+ if ($data[$key] instanceof Document) {
+ $data[$key] = $this->output($data[$key], $rule['type']);
+ }
+ }
+
+ $output[$key] = $data[$key];
+ }
+
+ $this->payload = $output;
+
+ return $this->payload;
+ }
+
+ /**
+ * Output response
+ *
+ * Generate HTTP response output including the response header (+cookies) and body and prints them.
+ *
+ * @param string $body
+ *
+ * @return void
+ */
+ public function file(string $body = ''): void
+ {
+ $this->payload = [
+ 'payload' => $body
+ ];
+
+ $this->send($body);
+ }
+
+ /**
+ * @return array
+ */
+ public function getPayload(): array
+ {
+ return $this->payload;
+ }
+
+ /**
+ * Function to set a response filter
+ *
+ * @param $filter the response filter to set
+ *
+ * @return void
+ */
+ public static function setFilter(?Filter $filter)
+ {
+ self::$filter = $filter;
+ }
+
+ /**
+ * Return the currently set filter
+ *
+ * @return Filter
+ */
+ public static function getFilter(): ?Filter
+ {
+ return self::$filter;
+ }
+
+ /**
+ * Check if a filter has been set
+ *
+ * @return bool
+ */
+ public static function hasFilter(): bool
+ {
+ return self::$filter != null;
+ }
+
+ /**
+ * Set Header
+ *
+ * @param string $key
+ * @param string $value
+ * @return void
+ */
+ public function setHeader(string $key, string $value): void
+ {
+ $this->sendHeader($key, $value);
+ }
+}
diff --git a/src/SDK/Language/CLI.php b/src/SDK/Language/CLI.php
index 0b894bf66..fed1e0388 100644
--- a/src/SDK/Language/CLI.php
+++ b/src/SDK/Language/CLI.php
@@ -142,6 +142,11 @@ public function getFiles(): array
'destination' => 'lib/config.js',
'template' => 'cli/lib/config.js.twig',
],
+ [
+ 'scope' => 'default',
+ 'destination' => 'lib/paginate.js',
+ 'template' => 'cli/lib/paginate.js.twig',
+ ],
[
'scope' => 'default',
'destination' => 'lib/client.js',
diff --git a/src/SDK/Language/Deno.php b/src/SDK/Language/Deno.php
index 1fdef0c0d..3027ed503 100644
--- a/src/SDK/Language/Deno.php
+++ b/src/SDK/Language/Deno.php
@@ -33,21 +33,41 @@ public function getFiles(): array
'destination' => 'src/permission.ts',
'template' => 'deno/src/permission.ts.twig',
],
+ [
+ 'scope' => 'default',
+ 'destination' => 'test/permission.test.ts',
+ 'template' => 'deno/test/permission.test.ts.twig',
+ ],
[
'scope' => 'default',
'destination' => 'src/role.ts',
'template' => 'deno/src/role.ts.twig',
],
+ [
+ 'scope' => 'default',
+ 'destination' => 'test/role.test.ts',
+ 'template' => 'deno/test/role.test.ts.twig',
+ ],
[
'scope' => 'default',
'destination' => 'src/id.ts',
'template' => 'deno/src/id.ts.twig',
],
+ [
+ 'scope' => 'default',
+ 'destination' => 'test/id.test.ts',
+ 'template' => 'deno/test/id.test.ts.twig',
+ ],
[
'scope' => 'default',
'destination' => 'src/query.ts',
'template' => 'deno/src/query.ts.twig',
],
+ [
+ 'scope' => 'default',
+ 'destination' => 'test/query.test.ts',
+ 'template' => 'deno/test/query.test.ts.twig',
+ ],
[
'scope' => 'default',
'destination' => 'src/inputFile.ts',
@@ -73,6 +93,11 @@ public function getFiles(): array
'destination' => '/src/services/{{service.name | caseDash}}.ts',
'template' => 'deno/src/services/service.ts.twig',
],
+ [
+ 'scope' => 'service',
+ 'destination' => '/test/services/{{service.name | caseDash}}.test.ts',
+ 'template' => 'deno/test/services/service.test.ts.twig',
+ ],
[
'scope' => 'default',
'destination' => 'README.md',
diff --git a/src/SDK/Language/DotNet.php b/src/SDK/Language/DotNet.php
index 709f56ea5..f569225f2 100644
--- a/src/SDK/Language/DotNet.php
+++ b/src/SDK/Language/DotNet.php
@@ -129,7 +129,8 @@ public function getKeywords(): array
'when',
'where',
'while',
- 'yield'
+ 'yield',
+ 'path'
];
}
diff --git a/src/SDK/Language/JS.php b/src/SDK/Language/JS.php
index 3ed496b24..c41727e36 100644
--- a/src/SDK/Language/JS.php
+++ b/src/SDK/Language/JS.php
@@ -105,6 +105,7 @@ public function getKeywords(): array
'while',
'with',
'yield',
+ 'path'
];
}
diff --git a/src/SDK/Language/Kotlin.php b/src/SDK/Language/Kotlin.php
index f6a2af02b..a7a4c5aea 100644
--- a/src/SDK/Language/Kotlin.php
+++ b/src/SDK/Language/Kotlin.php
@@ -86,7 +86,8 @@ public function getKeywords(): array
"vararg",
"when",
"where",
- "while"
+ "while",
+ "path"
];
}
diff --git a/src/SDK/Language/PHP.php b/src/SDK/Language/PHP.php
index 3f6968814..035621534 100644
--- a/src/SDK/Language/PHP.php
+++ b/src/SDK/Language/PHP.php
@@ -117,7 +117,8 @@ public function getKeywords(): array
'use',
'var',
'while',
- 'xor'
+ 'xor',
+ 'path'
];
}
@@ -176,21 +177,41 @@ public function getFiles(): array
'destination' => 'src/{{ spec.title | caseUcfirst}}/Permission.php',
'template' => 'php/src/Permission.php.twig',
],
+ [
+ 'scope' => 'default',
+ 'destination' => 'tests/{{ spec.title | caseUcfirst}}/PermissionTest.php',
+ 'template' => 'php/tests/PermissionTest.php.twig',
+ ],
[
'scope' => 'default',
'destination' => 'src/{{ spec.title | caseUcfirst}}/Role.php',
'template' => 'php/src/Role.php.twig',
],
+ [
+ 'scope' => 'default',
+ 'destination' => 'tests/{{ spec.title | caseUcfirst}}/RoleTest.php',
+ 'template' => 'php/tests/RoleTest.php.twig',
+ ],
[
'scope' => 'default',
'destination' => 'src/{{ spec.title | caseUcfirst}}/ID.php',
'template' => 'php/src/ID.php.twig',
],
+ [
+ 'scope' => 'default',
+ 'destination' => 'tests/{{ spec.title | caseUcfirst}}/IDTest.php',
+ 'template' => 'php/tests/IDTest.php.twig',
+ ],
[
'scope' => 'default',
'destination' => 'src/{{ spec.title | caseUcfirst}}/Query.php',
'template' => 'php/src/Query.php.twig',
],
+ [
+ 'scope' => 'default',
+ 'destination' => 'tests/{{ spec.title | caseUcfirst}}/QueryTest.php',
+ 'template' => 'php/tests/QueryTest.php.twig',
+ ],
[
'scope' => 'default',
'destination' => 'src/{{ spec.title | caseUcfirst}}/InputFile.php',
@@ -211,6 +232,11 @@ public function getFiles(): array
'destination' => '/src/{{ spec.title | caseUcfirst}}/Services/{{service.name | caseUcfirst}}.php',
'template' => 'php/src/Services/Service.php.twig',
],
+ [
+ 'scope' => 'service',
+ 'destination' => '/tests/{{ spec.title | caseUcfirst}}/Services/{{service.name | caseUcfirst}}Test.php',
+ 'template' => 'php/tests/Services/ServiceTest.php.twig',
+ ],
];
}
diff --git a/src/SDK/Language/Ruby.php b/src/SDK/Language/Ruby.php
index d05021146..eb50551b0 100644
--- a/src/SDK/Language/Ruby.php
+++ b/src/SDK/Language/Ruby.php
@@ -74,6 +74,7 @@ public function getKeywords(): array
'until',
'when',
'while',
+ 'path'
];
}
diff --git a/templates/android/library/src/main/java/io/appwrite/Client.kt.twig b/templates/android/library/src/main/java/io/appwrite/Client.kt.twig
index 930b74edd..210ef4fbe 100644
--- a/templates/android/library/src/main/java/io/appwrite/Client.kt.twig
+++ b/templates/android/library/src/main/java/io/appwrite/Client.kt.twig
@@ -374,7 +374,7 @@ class Client @JvmOverloads constructor(
responseType = Map::class.java,
)
val chunksUploaded = current["chunksUploaded"] as Long
- offset = (chunksUploaded * CHUNK_SIZE).coerceAtMost(size)
+ offset = chunksUploaded * CHUNK_SIZE
}
while (offset < size) {
@@ -385,7 +385,7 @@ class Client @JvmOverloads constructor(
}
"bytes" -> {
val end = if (offset + CHUNK_SIZE < size) {
- offset + CHUNK_SIZE
+ offset + CHUNK_SIZE - 1
} else {
size - 1
}
@@ -405,7 +405,7 @@ class Client @JvmOverloads constructor(
)
headers["Content-Range"] =
- "bytes $offset-${((offset + CHUNK_SIZE) - 1).coerceAtMost(size)}/$size"
+ "bytes $offset-${((offset + CHUNK_SIZE) - 1).coerceAtMost(size - 1)}/$size"
result = call(
method = "POST",
diff --git a/templates/android/library/src/main/java/io/appwrite/Query.kt.twig b/templates/android/library/src/main/java/io/appwrite/Query.kt.twig
index e9c5a9c88..7f4fbcd81 100644
--- a/templates/android/library/src/main/java/io/appwrite/Query.kt.twig
+++ b/templates/android/library/src/main/java/io/appwrite/Query.kt.twig
@@ -20,11 +20,11 @@ class Query {
fun isNotNull(attribute: String) = "isNotNull(\"${attribute}\")"
- fun between(attribute: String, start: Int, end: Int) = Query.addQuery(attribute, "between", listOf(start, end))
+ fun between(attribute: String, start: Int, end: Int) = "between(\"${attribute}\", ${start}, ${end})"
- fun between(attribute: String, start: Double, end: Double) = Query.addQuery(attribute, "between", listOf(start, end))
+ fun between(attribute: String, start: Double, end: Double) = "between(\"${attribute}\", ${start}, ${end})"
- fun between(attribute: String, start: String, end: String) = Query.addQuery(attribute, "between", listOf(start, end))
+ fun between(attribute: String, start: String, end: String) = "between(\"${attribute}\", \"${start}\", \"${end}\")"
fun startsWith(attribute: String, value: String) = Query.addQuery(attribute, "startsWith", value)
diff --git a/templates/android/library/src/main/java/io/appwrite/Role.kt.twig b/templates/android/library/src/main/java/io/appwrite/Role.kt.twig
index d5f59c3a6..2e4de9861 100644
--- a/templates/android/library/src/main/java/io/appwrite/Role.kt.twig
+++ b/templates/android/library/src/main/java/io/appwrite/Role.kt.twig
@@ -1,29 +1,72 @@
package {{ sdk.namespace | caseDot }}
+/**
+ * Helper class to generate role strings for [Permission].
+ */
class Role {
companion object {
+
+ /**
+ * Grants access to anyone.
+ *
+ * This includes authenticated and unauthenticated users.
+ */
fun any(): String = "any"
+ /**
+ * Grants access to a specific user by user ID.
+ *
+ * You can optionally pass verified or unverified for
+ * [status] to target specific types of users.
+ */
fun user(id: String, status: String = ""): String = if(status.isEmpty()) {
"user:$id"
} else {
"user:$id/$status"
}
+ /**
+ * Grants access to any authenticated or anonymous user.
+ *
+ * You can optionally pass verified or unverified for
+ * [status] to target specific types of users.
+ */
fun users(status: String = ""): String = if(status.isEmpty()) {
"users"
} else {
"users/$status"
}
+ /**
+ * Grants access to any guest user without a session.
+ *
+ * Authenticated users don't have access to this role.
+ */
fun guests(): String = "guests"
+ /**
+ * Grants access to a team by team ID.
+ *
+ * You can optionally pass a role for [role] to target
+ * team members with the specified role.
+ */
fun team(id: String, role: String = ""): String = if(role.isEmpty()) {
"team:$id"
} else {
"team:$id/$role"
}
+ /**
+ * Grants access to a specific member of a team.
+ *
+ * When the member is removed from the team, they will
+ * no longer have access.
+ */
fun member(id: String): String = "member:$id"
+
+ /**
+ * Grants access to a user with the specified label.
+ */
+ fun label(name: String): String = "label:$name"
}
}
\ No newline at end of file
diff --git a/templates/android/library/src/main/java/io/appwrite/services/ServiceTemplate.kt.twig b/templates/android/library/src/main/java/io/appwrite/services/ServiceTemplate.kt.twig
index f2a331c4e..a461b5f91 100644
--- a/templates/android/library/src/main/java/io/appwrite/services/ServiceTemplate.kt.twig
+++ b/templates/android/library/src/main/java/io/appwrite/services/ServiceTemplate.kt.twig
@@ -53,12 +53,12 @@ class {{ service.name | caseUcfirst }} : Service {
onProgress: ((UploadProgress) -> Unit)? = null
{%~ endif %}
){% if method.type != "webAuth" %}: {{ method | returnType(spec, sdk.namespace | caseDot) | raw }}{% endif %} {
- val path = "{{ method.path }}"
+ val apiPath = "{{ method.path }}"
{%~ for parameter in method.parameters.path %}
.replace("{{ '{' ~ parameter.name | caseCamel ~ '}' }}", {{ parameter.name | caseCamel }})
{%~ endfor %}
- val params = mutableMapOf(
+ val apiParams = mutableMapOf(
{%~ for parameter in method.parameters.query | merge(method.parameters.body) %}
"{{ parameter.name }}" to {{ parameter.name | caseCamel }},
{%~ endfor %}
@@ -73,25 +73,25 @@ class {{ service.name | caseUcfirst }} : Service {
{%~ endif %}
)
{%~ if method.type == 'webAuth' %}
- val query = mutableListOf()
- params.forEach {
+ val apiQuery = mutableListOf()
+ apiParams.forEach {
when (it.value) {
null -> {
return@forEach
}
is List<*> -> {
- query.add("${it.key}[]=${it.value.toString()}")
+ apiQuery.add("${it.key}[]=${it.value.toString()}")
}
else -> {
- query.add("${it.key}=${it.value.toString()}")
+ apiQuery.add("${it.key}=${it.value.toString()}")
}
}
}
- val url = Uri.parse("${client.endPoint}${path}?${query.joinToString("&")}")
+ val apiUrl = Uri.parse("${client.endPoint}${apiPath}?${apiQuery.joinToString("&")}")
val callbackUrlScheme = "{{ spec.title | caseLower }}-callback-${client.config["project"]}"
- WebAuthComponent.authenticate(activity, url, callbackUrlScheme) {
+ WebAuthComponent.authenticate(activity, apiUrl, callbackUrlScheme) {
if (it.isFailure) {
throw it.exceptionOrNull()!!
}
@@ -118,12 +118,12 @@ class {{ service.name | caseUcfirst }} : Service {
{%~ elseif method.type == 'location' %}
return client.call(
"{{ method.method | caseUpper }}",
- path,
- params = params,
+ apiPath,
+ params = apiParams,
responseType = {{ method | returnType(spec, sdk.namespace | caseDot) | raw }}::class.java
)
{%~ else %}
- val headers = mutableMapOf(
+ val apiHeaders = mutableMapOf(
{%~ for key, header in method.headers %}
"{{ key }}" to "{{ header }}",
{%~ endfor %}
@@ -146,9 +146,9 @@ class {{ service.name | caseUcfirst }} : Service {
{%~ endif %}
{%~ endfor %}
return client.chunkedUpload(
- path,
- headers,
- params,
+ apiPath,
+ apiHeaders,
+ apiParams,
responseType = {{ method | returnType(spec, sdk.namespace | caseDot) | raw }}::class.java,
{%~ if method.responseModel %}
converter,
@@ -160,9 +160,9 @@ class {{ service.name | caseUcfirst }} : Service {
{%~ else %}
return client.call(
"{{ method.method | caseUpper }}",
- path,
- headers,
- params,
+ apiPath,
+ apiHeaders,
+ apiParams,
{%~ if method.responseModel | hasGenericType(spec) %}
responseType = classOf(),
{%~ else %}
diff --git a/templates/cli/lib/commands/command.js.twig b/templates/cli/lib/commands/command.js.twig
index 3c9addc23..52bc99044 100644
--- a/templates/cli/lib/commands/command.js.twig
+++ b/templates/cli/lib/commands/command.js.twig
@@ -21,7 +21,7 @@ const {{ service.name | caseLower }}{{ method.name | caseUcfirst }} = async ({ {
{% endfor %}
let client = !sdk ? await {% if service.name == "projects" %}sdkForConsole(){% else %}sdkForProject(){% endif %} : sdk;
- let path = '{{ method.path }}'{% for parameter in method.parameters.path %}.replace('{{ '{' }}{{ parameter.name | caseCamel }}{{ '}' }}', {{ parameter.name | caseCamel | escapeKeyword }}){% endfor %};
+ let apiPath = '{{ method.path }}'{% for parameter in method.parameters.path %}.replace('{{ '{' }}{{ parameter.name | caseCamel }}{{ '}' }}', {{ parameter.name | caseCamel | escapeKeyword }}){% endfor %};
let payload = {};
{% if method.parameters.query|length > 0 %}
@@ -95,9 +95,9 @@ const {{ service.name | caseLower }}{{ method.name | caseUcfirst }} = async ({ {
payload['project'] = localConfig.getProject().projectId
payload['key'] = globalConfig.getKey();
const queryParams = new URLSearchParams(payload);
- path = `${globalConfig.getEndpoint()}${path}?${queryParams.toString()}`;
+ apiPath = `${globalConfig.getEndpoint()}${apiPath}?${queryParams.toString()}`;
- const response = await client.call('{{ method.method | caseLower }}', path, {
+ const response = await client.call('{{ method.method | caseLower }}', apiPath, {
{% for parameter in method.parameters.header %}
'{{ parameter.name }}': ${{ parameter.name | caseCamel | escapeKeyword }},
{% endfor %}
@@ -122,7 +122,7 @@ const {{ service.name | caseLower }}{{ method.name | caseUcfirst }} = async ({ {
if (size <= libClient.CHUNK_SIZE) {
payload['{{ parameter.name }}'] = fs.createReadStream(payload['{{ parameter.name }}']);
- response = await client.call('{{ method.method | caseLower }}', path, {
+ response = await client.call('{{ method.method | caseLower }}', apiPath, {
{% for parameter in method.parameters.header %}
'{{ parameter.name }}': ${{ parameter.name | caseCamel | escapeKeyword }},
{% endfor %}
@@ -136,12 +136,8 @@ const {{ service.name | caseLower }}{{ method.name | caseUcfirst }} = async ({ {
} else {
const streamFilePath = payload['{{ parameter.name }}'];
- let id = undefined;
- let counter = 0;
- const totalCounters = Math.ceil(size / libClient.CHUNK_SIZE);
-
- const headers = {
+ const apiHeaders = {
{% for parameter in method.parameters.header %}
'{{ parameter.name }}': ${{ parameter.name | caseCamel | escapeKeyword }},
{% endfor %}
@@ -150,55 +146,50 @@ const {{ service.name | caseLower }}{{ method.name | caseUcfirst }} = async ({ {
{% endfor %}
};
+ let offset = 0;
{% for parameter in method.parameters.all %}
{% if parameter.isUploadID %}
if({{ parameter.name | caseCamel | escapeKeyword }} != 'unique()') {
try {
- response = await client.call('get', path + '/' + {{ parameter.name }}, headers);
- counter = response.chunksUploaded;
+ response = await client.call('get', apiPath + '/' + {{ parameter.name }}, apiHeaders);
+ offset = response.chunksUploaded * libClient.CHUNK_SIZE;
} catch(e) {
}
}
{% endif %}
{% endfor %}
- for (counter; counter < totalCounters; counter++) {
- const start = (counter * libClient.CHUNK_SIZE);
- const end = Math.min((((counter * libClient.CHUNK_SIZE) + libClient.CHUNK_SIZE) - 1), size);
-
- headers['content-range'] = 'bytes ' + start + '-' + end + '/' + size;
+ while (offset < size) {
+ let end = Math.min(offset + libClient.CHUNK_SIZE - 1, size - 1);
- if (id) {
- headers['x-appwrite-id'] = id;
+ apiHeaders['content-range'] = 'bytes ' + offset + '-' + end + '/' + size;
+ if (response && response.$id) {
+ apiHeaders['x-{{spec.title | caseLower }}-id'] = response.$id;
}
const stream = fs.createReadStream(streamFilePath, {
- start,
+ start: offset,
end
});
payload['{{ parameter.name }}'] = stream;
+ response = await client.call('{{ method.method | caseLower }}', apiPath, apiHeaders, payload{% if method.type == 'location' %}, 'arraybuffer'{% endif %});
- response = await client.call('{{ method.method | caseLower }}', path, headers, payload{% if method.type == 'location' %}, 'arraybuffer'{% endif %});
-
- if (!id) {
- id = response['$id'];
- }
-
- if (onProgress !== null) {
+ if (onProgress) {
onProgress({
- $id: response['$id'],
- progress: Math.min((counter+1) * libClient.CHUNK_SIZE, size) / size * 100,
- sizeUploaded: end+1,
- chunksTotal: response['chunksTotal'],
- chunksUploaded: response['chunksUploaded']
+ $id: response.$id,
+ progress: ( offset / size ) * 100,
+ sizeUploaded: offset,
+ chunksTotal: response.chunksTotal,
+ chunksUploaded: response.chunksUploaded
});
}
+ offset += libClient.CHUNK_SIZE;
}
}
{% endif %}
{% endfor %}
{% else %}
- response = await client.call('{{ method.method | caseLower }}', path, {
+ response = await client.call('{{ method.method | caseLower }}', apiPath, {
{% for parameter in method.parameters.header %}
'{{ parameter.name }}': ${{ parameter.name | caseCamel | escapeKeyword }},
{% endfor %}
diff --git a/templates/cli/lib/commands/deploy.js.twig b/templates/cli/lib/commands/deploy.js.twig
index f7c5ec043..420bd3f85 100644
--- a/templates/cli/lib/commands/deploy.js.twig
+++ b/templates/cli/lib/commands/deploy.js.twig
@@ -2,6 +2,7 @@ const inquirer = require("inquirer");
const JSONbig = require("json-bigint")({ storeAsString: false });
const { Command } = require("commander");
const { localConfig } = require("../config");
+const { paginate } = require('../paginate');
const { questionsDeployBuckets, questionsDeployTeams, questionsDeployFunctions, questionsGetEntrypoint, questionsDeployCollections, questionsConfirmDeployCollections } = require("../questions");
const { actionRunner, success, log, error, commandDescriptions } = require("../parser");
const { functionsGet, functionsCreate, functionsUpdate, functionsCreateDeployment, functionsUpdateDeployment, functionsListVariables, functionsDeleteVariable, functionsCreateVariable } = require('./functions');
@@ -37,113 +38,163 @@ const {
teamsCreate
} = require("./teams");
-const POOL_DEBOUNCE = 2000; // in milliseconds
-const POOL_MAX_DEBOUNCES = 30;
+const STEP_SIZE = 100; // Resources
+const POOL_DEBOUNCE = 2000; // Milliseconds
+
+let poolMaxDebounces = 30;
const awaitPools = {
wipeAttributes: async (databaseId, collectionId, iteration = 1) => {
- if (iteration > POOL_MAX_DEBOUNCES) {
+ if (iteration > poolMaxDebounces) {
return false;
}
- // TODO: Pagination?
- const { attributes: remoteAttributes } = await databasesListAttributes({
+ const { total } = await databasesListAttributes({
databaseId,
collectionId,
- queries: ['limit(100)'],
+ queries: ['limit(1)'],
parseOutput: false
});
- if (remoteAttributes.length <= 0) {
+ if (total === 0) {
return true;
}
+ let steps = Math.max(1, Math.ceil(total / STEP_SIZE));
+ if (steps > 1 && iteration === 1) {
+ poolMaxDebounces *= steps;
+
+ log('Found a large number of attributes, increasing timeout to ' + (poolMaxDebounces * POOL_DEBOUNCE / 1000 / 60) + ' minutes')
+ }
+
await new Promise(resolve => setTimeout(resolve, POOL_DEBOUNCE));
- return await awaitPools.wipeAttributes(databaseId, collectionId, iteration + 1);
+
+ return await awaitPools.wipeAttributes(
+ databaseId,
+ collectionId,
+ iteration + 1
+ );
},
wipeIndexes: async (databaseId, collectionId, iteration = 1) => {
- if (iteration > POOL_MAX_DEBOUNCES) {
+ if (iteration > poolMaxDebounces) {
return false;
}
- // TODO: Pagination?
- const { indexes: remoteIndexes } = await databasesListIndexes({
+ const { total } = await databasesListIndexes({
databaseId,
collectionId,
queries: ['limit(100)'],
parseOutput: false
});
- if (remoteIndexes.length <= 0) {
+ if (total === 0) {
return true;
}
+ let steps = Math.max(1, Math.ceil(total / STEP_SIZE));
+ if (steps > 1 && iteration === 1) {
+ poolMaxDebounces *= steps;
+
+ log('Found a large number of indexes, increasing timeout to ' + (poolMaxDebounces * POOL_DEBOUNCE / 1000 / 60) + ' minutes')
+ }
+
await new Promise(resolve => setTimeout(resolve, POOL_DEBOUNCE));
- return await awaitPools.wipeIndexes(databaseId, collectionId, iteration + 1);
+
+ return await awaitPools.wipeIndexes(
+ databaseId,
+ collectionId,
+ iteration + 1
+ );
},
expectAttributes: async (databaseId, collectionId, attributeKeys, iteration = 1) => {
- if (iteration > POOL_MAX_DEBOUNCES) {
+ if (iteration > poolMaxDebounces) {
return false;
}
- // TODO: Pagination?
- const { attributes: remoteAttributes } = await databasesListAttributes({
+ let steps = Math.max(1, Math.ceil(attributeKeys.length / STEP_SIZE));
+ if (steps > 1 && iteration === 1) {
+ poolMaxDebounces *= steps;
+
+ log('Creating a large number of attributes, increasing timeout to ' + (poolMaxDebounces * POOL_DEBOUNCE / 1000 / 60) + ' minutes')
+ }
+
+ const { attributes } = await paginate(databasesListAttributes, {
databaseId,
collectionId,
- queries: ['limit(100)'],
parseOutput: false
- });
+ }, 100, 'attributes');
- const readyAttributeKeys = remoteAttributes.filter((attribute) => {
- if (attributeKeys.includes(attribute.key)) {
- if (['stuck', 'failed'].includes(attribute.status)) {
- throw new Error(`Attribute '${attribute.key}' failed!`);
- }
+ const ready = attributes
+ .filter(attribute => {
+ if (attributeKeys.includes(attribute.key)) {
+ if (['stuck', 'failed'].includes(attribute.status)) {
+ throw new Error(`Attribute '${attribute.key}' failed!`);
+ }
- return attribute.status === 'available';
- }
+ return attribute.status === 'available';
+ }
- return false;
- }).map(attribute => attribute.key);
+ return false;
+ })
+ .map(attribute => attribute.key);
- if (readyAttributeKeys.length >= attributeKeys.length) {
+ if (ready.length === attributeKeys.length) {
return true;
}
await new Promise(resolve => setTimeout(resolve, POOL_DEBOUNCE));
- return await awaitPools.expectAttributes(databaseId, collectionId, attributeKeys, iteration + 1);
+
+ return await awaitPools.expectAttributes(
+ databaseId,
+ collectionId,
+ attributeKeys,
+ iteration + 1
+ );
},
expectIndexes: async (databaseId, collectionId, indexKeys, iteration = 1) => {
- if (iteration > POOL_MAX_DEBOUNCES) {
+ if (iteration > poolMaxDebounces) {
return false;
}
- // TODO: Pagination?
- const { indexes: remoteIndexes } = await databasesListIndexes({
+ let steps = Math.max(1, Math.ceil(indexKeys.length / STEP_SIZE));
+ if (steps > 1 && iteration === 1) {
+ poolMaxDebounces *= steps;
+
+ log('Creating a large number of indexes, increasing timeout to ' + (poolMaxDebounces * POOL_DEBOUNCE / 1000 / 60) + ' minutes')
+ }
+
+ const { indexes } = await paginate(databasesListIndexes, {
databaseId,
collectionId,
- queries: ['limit(100)'],
parseOutput: false
- });
+ }, 100, 'indexes');
- const readyIndexKeys = remoteIndexes.filter((index) => {
- if (indexKeys.includes(index.key)) {
- if (['stuck', 'failed'].includes(index.status)) {
- throw new Error(`Index '${index.key}' failed!`);
- }
+ const ready = indexes
+ .filter((index) => {
+ if (indexKeys.includes(index.key)) {
+ if (['stuck', 'failed'].includes(index.status)) {
+ throw new Error(`Index '${index.key}' failed!`);
+ }
- return index.status === 'available';
- }
+ return index.status === 'available';
+ }
- return false;
- }).map(index => index.key);
+ return false;
+ })
+ .map(index => index.key);
- if (readyIndexKeys.length >= indexKeys.length) {
+ if (ready.length >= indexKeys.length) {
return true;
}
await new Promise(resolve => setTimeout(resolve, POOL_DEBOUNCE));
- return await awaitPools.expectIndexes(databaseId, collectionId, indexKeys, iteration + 1);
+
+ return await awaitPools.expectIndexes(
+ databaseId,
+ collectionId,
+ indexKeys,
+ iteration + 1
+ );
},
}
@@ -206,10 +257,14 @@ const deployFunction = async ({ functionId, all, yes } = {}) => {
functionId: func['$id'],
name: func.name,
execute: func.execute,
- vars: JSON.stringify(response.vars),
events: func.events,
schedule: func.schedule,
timeout: func.timeout,
+ enabled: func.enabled,
+ logging: func.logging,
+ entrypoint: func.entrypoint,
+ commands: func.commands,
+ vars: JSON.stringify(response.vars),
parseOutput: false
});
} catch (e) {
@@ -220,10 +275,14 @@ const deployFunction = async ({ functionId, all, yes } = {}) => {
name: func.name,
runtime: func.runtime,
execute: func.execute,
- vars: JSON.stringify(func.vars),
events: func.events,
schedule: func.schedule,
timeout: func.timeout,
+ enabled: func.enabled,
+ logging: func.logging,
+ entrypoint: func.entrypoint,
+ commands: func.commands,
+ vars: JSON.stringify(func.vars),
parseOutput: false
});
@@ -241,19 +300,19 @@ const deployFunction = async ({ functionId, all, yes } = {}) => {
if (func.variables) {
// Delete existing variables
- // TODO: Pagination?
- const { variables: remoteVariables } = await functionsListVariables({
+ const { total } = await functionsListVariables({
functionId: func['$id'],
- queries: ['limit(100)'],
+ queries: ['limit(1)'],
parseOutput: false
});
let deployVariables = yes;
- if (remoteVariables.length == 0) {
+
+ if (total === 0) {
deployVariables = true;
} else if (remoteVariables.length > 0 && !yes) {
const variableAnswers = await inquirer.prompt(questionsDeployFunctions[1])
- deployVariables = variableAnswers.override === "YES";
+ deployVariables = variableAnswers.override.toLowerCase() === "yes";
}
if (!deployVariables) {
@@ -283,7 +342,7 @@ const deployFunction = async ({ functionId, all, yes } = {}) => {
// Create tag
if (!func.entrypoint) {
- answers = await inquirer.prompt(questionsGetEntrypoint)
+ const answers = await inquirer.prompt(questionsGetEntrypoint)
func.entrypoint = answers.entrypoint;
localConfig.updateFunction(func['$id'], func);
}
@@ -292,6 +351,7 @@ const deployFunction = async ({ functionId, all, yes } = {}) => {
response = await functionsCreateDeployment({
functionId: func['$id'],
entrypoint: func.entrypoint,
+ commands: func.commands,
code: func.path,
activate: true,
parseOutput: false
@@ -464,22 +524,27 @@ const deployCollection = async ({ all, yes } = {}) => {
databaseId: collection.databaseId,
parseOutput: false,
});
+
databaseId = database.$id;
- await databasesUpdate({
- databaseId: collection.databaseId,
- name: localDatabase.name ?? collection.databaseId,
- parseOutput: false
- })
+ if (database.name !== (localDatabase.name ?? collection.databaseId)) {
+ await databasesUpdate({
+ databaseId: collection.databaseId,
+ name: localDatabase.name ?? collection.databaseId,
+ parseOutput: false
+ })
- success(`Updated ${localDatabase.name} ( ${collection.databaseId} )`);
+ success(`Updated ${localDatabase.name} ( ${collection.databaseId} )`);
+ }
} catch (err) {
log(`Database ${collection.databaseId} not found. Creating it now...`);
+
const database = await databasesCreate({
databaseId: collection.databaseId,
name: localDatabase.name ?? collection.databaseId,
parseOutput: false,
});
+
databaseId = database.$id;
}
@@ -489,11 +554,12 @@ const deployCollection = async ({ all, yes } = {}) => {
collectionId: collection['$id'],
parseOutput: false,
})
+
log(`Collection ${collection.name} ( ${collection['$id']} ) already exists.`);
if (!yes) {
- answers = await inquirer.prompt(questionsDeployCollections[1])
- if (answers.override !== "YES") {
+ const answers = await inquirer.prompt(questionsDeployCollections[1])
+ if (answers.override.toLowerCase() !== "yes") {
log(`Received "${answers.override}". Skipping ${collection.name} ( ${collection['$id']} )`);
continue;
}
@@ -501,15 +567,13 @@ const deployCollection = async ({ all, yes } = {}) => {
log(`Deleting indexes and attributes ... `);
- // TODO: Pagination?
- const { indexes: remoteIndexes } = await databasesListIndexes({
+ const { indexes } = await paginate(databasesListIndexes, {
databaseId,
collectionId: collection['$id'],
- queries: ['limit(100)'],
parseOutput: false
- });
+ }, 100, 'indexes');
- await Promise.all(remoteIndexes.map(async index => {
+ await Promise.all(indexes.map(async index => {
await databasesDeleteIndex({
databaseId,
collectionId: collection['$id'],
@@ -518,20 +582,18 @@ const deployCollection = async ({ all, yes } = {}) => {
});
}));
- const deleteIndexesPoolStatus = await awaitPools.wipeIndexes(databaseId, collection['$id']);
- if (!deleteIndexesPoolStatus) {
- throw new Error("Index deletion did not finish for too long.");
+ let result = await awaitPools.wipeIndexes(databaseId, collection['$id']);
+ if (!result) {
+ throw new Error("Index deletion timed out.");
}
- // TODO: Pagination?
- const { attributes: remoteAttributes } = await databasesListAttributes({
+ const { attributes } = await paginate(databasesListAttributes, {
databaseId,
collectionId: collection['$id'],
- queries: ['limit(100)'],
parseOutput: false
- });
+ }, 100, 'attributes');
- await Promise.all(remoteAttributes.map(async attribute => {
+ await Promise.all(attributes.map(async attribute => {
await databasesDeleteAttribute({
databaseId,
collectionId: collection['$id'],
@@ -542,7 +604,7 @@ const deployCollection = async ({ all, yes } = {}) => {
const deleteAttributesPoolStatus = await awaitPools.wipeAttributes(databaseId, collection['$id']);
if (!deleteAttributesPoolStatus) {
- throw new Error("Attribute deletion did not finish for too long.");
+ throw new Error("Attribute deletion timed out.");
}
await databasesUpdateCollection({
@@ -572,20 +634,26 @@ const deployCollection = async ({ all, yes } = {}) => {
}
// Create all non-relationship attributes first
- const nonRelationshipAttributes = collection.attributes.filter(attribute => attribute.type !== 'relationship');
- await Promise.all(nonRelationshipAttributes.map(attribute => {
+ const attributes = collection.attributes.filter(attribute => attribute.type !== 'relationship');
+
+ await Promise.all(attributes.map(attribute => {
return createAttribute(databaseId, collection['$id'], attribute);
}));
- const nonRelationshipAttributeKeys = nonRelationshipAttributes.map(attribute => attribute.key);
- const createPoolStatus = await awaitPools.expectAttributes(databaseId, collection['$id'], nonRelationshipAttributeKeys);
- if (!createPoolStatus) {
- throw new Error("Attribute creation did not finish for too long.");
+ let result = await awaitPools.expectAttributes(
+ databaseId,
+ collection['$id'],
+ attributes.map(attribute => attribute.key)
+ );
+
+ if (!result) {
+ throw new Error("Attribute creation timed out.");
}
- success(`Created ${nonRelationshipAttributeKeys.length} non-relationship attributes`);
+ success(`Created ${attributes.length} non-relationship attributes`);
log(`Creating indexes ...`)
+
await Promise.all(collection.indexes.map(async index => {
await databasesCreateIndex({
databaseId,
@@ -598,10 +666,14 @@ const deployCollection = async ({ all, yes } = {}) => {
});
}));
- const indexKeys = collection.indexes.map(attribute => attribute.key);
- const indexPoolStatus = await awaitPools.expectIndexes(databaseId, collection['$id'], indexKeys);
- if (!indexPoolStatus) {
- throw new Error("Index creation did not finish for too long.");
+ result = await awaitPools.expectIndexes(
+ databaseId,
+ collection['$id'],
+ collection.indexes.map(attribute => attribute.key)
+ );
+
+ if (!result) {
+ throw new Error("Index creation timed out.");
}
success(`Created ${collection.indexes.length} indexes`);
@@ -611,23 +683,31 @@ const deployCollection = async ({ all, yes } = {}) => {
// Create the relationship attributes
for (let collection of collections) {
- const relationshipAttributes = collection.attributes.filter(attribute => attribute.type === 'relationship' && attribute.side === 'parent');
+ const relationships = collection.attributes.filter(attribute =>
+ attribute.type === 'relationship' && attribute.side === 'parent'
+ );
- if (relationshipAttributes.length === 0) continue;
+ if (relationships.length === 0) {
+ continue;
+ }
log(`Deploying relationships for collection ${collection.name} ( ${collection['$id']} )`);
- await Promise.all(relationshipAttributes.map(attribute => {
+ await Promise.all(relationships.map(attribute => {
return createAttribute(collection['databaseId'], collection['$id'], attribute);
}));
- const nonRelationshipAttributeKeys = relationshipAttributes.map(attribute => attribute.key);
- const createPoolStatus = await awaitPools.expectAttributes(collection['databaseId'], collection['$id'], nonRelationshipAttributeKeys);
- if (!createPoolStatus) {
- throw new Error("Attribute creation did not finish for too long.");
+ let result = await awaitPools.expectAttributes(
+ collection['databaseId'],
+ collection['$id'],
+ relationships.map(attribute => attribute.key)
+ );
+
+ if (!result) {
+ throw new Error("Attribute creation timed out.");
}
- success(`Created ${nonRelationshipAttributeKeys.length} relationship attributes`);
+ success(`Created ${relationships.length} relationship attributes`);
}
}
@@ -645,7 +725,7 @@ const deployBucket = async ({ all, yes } = {}) => {
}
if (bucketIds.length === 0) {
- let answers = await inquirer.prompt(questionsDeployBuckets[0])
+ const answers = await inquirer.prompt(questionsDeployBuckets[0])
bucketIds.push(...answers.buckets);
}
@@ -667,8 +747,8 @@ const deployBucket = async ({ all, yes } = {}) => {
log(`Bucket ${bucket.name} ( ${bucket['$id']} ) already exists.`);
if (!yes) {
- answers = await inquirer.prompt(questionsDeployBuckets[1])
- if (answers.override !== "YES") {
+ const answers = await inquirer.prompt(questionsDeployBuckets[1])
+ if (answers.override.toLowerCase() !== "yes") {
log(`Received "${answers.override}". Skipping ${bucket.name} ( ${bucket['$id']} )`);
continue;
}
@@ -732,7 +812,7 @@ const deployTeam = async ({ all, yes } = {}) => {
}
if (teamIds.length === 0) {
- let answers = await inquirer.prompt(questionsDeployTeams[0])
+ const answers = await inquirer.prompt(questionsDeployTeams[0])
teamIds.push(...answers.teams);
}
@@ -754,8 +834,8 @@ const deployTeam = async ({ all, yes } = {}) => {
log(`Team ${team.name} ( ${team['$id']} ) already exists.`);
if (!yes) {
- answers = await inquirer.prompt(questionsDeployTeams[1])
- if (answers.override !== "YES") {
+ const answers = await inquirer.prompt(questionsDeployTeams[1])
+ if (answers.override.toLowerCase() !== "yes") {
log(`Received "${answers.override}". Skipping ${team.name} ( ${team['$id']} )`);
continue;
}
@@ -799,7 +879,7 @@ deploy
deploy
.command("collection")
.description("Deploy collections in the current project.")
- .option(`--all`, `Flag to deploy all functions`)
+ .option(`--all`, `Flag to deploy all collections`)
.option(`--yes`, `Flag to confirm all warnings`)
.action(actionRunner(deployCollection));
diff --git a/templates/cli/lib/commands/generic.js.twig b/templates/cli/lib/commands/generic.js.twig
index cdd1bea2c..b212d9160 100644
--- a/templates/cli/lib/commands/generic.js.twig
+++ b/templates/cli/lib/commands/generic.js.twig
@@ -82,6 +82,7 @@ const client = new Command("client")
}
let client = new Client().setEndpoint(endpoint);
+ client.setProject('console');
if (selfSigned || globalConfig.getSelfSigned()) {
client.setSelfSigned(true);
}
diff --git a/templates/cli/lib/commands/init.js.twig b/templates/cli/lib/commands/init.js.twig
index 06e400d70..4e4f1a39f 100644
--- a/templates/cli/lib/commands/init.js.twig
+++ b/templates/cli/lib/commands/init.js.twig
@@ -10,6 +10,7 @@ const { databasesGet, databasesListCollections, databasesList } = require("./dat
const { storageListBuckets } = require("./storage");
const { sdkForConsole } = require("../sdks");
const { localConfig } = require("../config");
+const { paginate } = require("../paginate");
const { questionsInitProject, questionsInitFunction, questionsInitCollection } = require("../questions");
const { success, log, actionRunner, commandDescriptions } = require("../parser");
@@ -24,11 +25,11 @@ const init = new Command("init")
const initProject = async () => {
let response = {}
- let answers = await inquirer.prompt(questionsInitProject)
+ const answers = await inquirer.prompt(questionsInitProject)
if (!answers.project) process.exit(1)
let sdk = await sdkForConsole();
- if (answers.start == "new") {
+ if (answers.start === "new") {
response = await teamsCreate({
teamId: 'unique()',
name: answers.project,
@@ -53,8 +54,8 @@ const initProject = async () => {
const initFunction = async () => {
// TODO: Add CI/CD support (ID, name, runtime)
- let answers = await inquirer.prompt(questionsInitFunction)
- let functionFolder = path.join(process.cwd(), 'functions');
+ const answers = await inquirer.prompt(questionsInitFunction)
+ const functionFolder = path.join(process.cwd(), 'functions');
if (!fs.existsSync(functionFolder)) {
fs.mkdirSync(functionFolder, {
@@ -72,21 +73,27 @@ const initFunction = async () => {
log(`Entrypoint for this runtime not found. You will be asked to configure entrypoint when you first deploy the function.`);
}
+ if (!answers.runtime.commands) {
+ log(`Installation command for this runtime not found. You will be asked to configure the install command when you first deploy the function.`);
+ }
+
let response = await functionsCreate({
functionId: answers.id,
name: answers.name,
runtime: answers.runtime.id,
+ entrypoint: answers.runtime.entrypoint || '',
+ commands: answers.runtime.commands || '',
parseOutput: false
})
fs.mkdirSync(functionDir, "777");
- let gitInitCommands = "git clone --depth 1 --sparse https://github.com/{{ sdk.gitUserName }}/functions-starter ."; // depth prevents fetching older commits reducing the amount fetched
+ let gitInitCommands = "git clone -b v3 --single-branch --depth 1 --sparse https://github.com/{{ sdk.gitUserName }}/functions-starter ."; // depth prevents fetching older commits reducing the amount fetched
let gitPullCommands = `git sparse-checkout add ${answers.runtime.id}`;
/* Force use CMD as powershell does not support && */
- if (process.platform == 'win32') {
+ if (process.platform === 'win32') {
gitInitCommands = 'cmd /c "' + gitInitCommands + '"';
gitPullCommands = 'cmd /c "' + gitPullCommands + '"';
}
@@ -138,13 +145,16 @@ const initFunction = async () => {
$id: response['$id'],
name: response.name,
runtime: response.runtime,
- path: `functions/${answers.name}`,
- entrypoint: answers.runtime.entrypoint || '',
- ignore: answers.runtime.ignore || null,
execute: response.execute,
events: response.events,
schedule: response.schedule,
timeout: response.timeout,
+ enabled: response.enabled,
+ logging: response.logging,
+ entrypoint: response.entrypoint,
+ commands: response.commands,
+ ignore: answers.runtime.ignore || null,
+ path: `functions/${answers.name}`,
};
localConfig.addFunction(data);
@@ -178,15 +188,12 @@ const initCollection = async ({ all, databaseId } = {}) => {
localConfig.addDatabase(database);
- // TODO: Pagination?
- let response = await databasesListCollections({
+ const { collections, total } = await paginate(databasesListCollections, {
databaseId,
- queries: ['limit(100)'],
parseOutput: false
- })
+ }, 100, 'collections');
- let collections = response.collections;
- log(`Found ${collections.length} collections`);
+ log(`Found ${total} collections`);
collections.forEach(async collection => {
log(`Fetching ${collection.name} ...`);
@@ -202,13 +209,8 @@ const initCollection = async ({ all, databaseId } = {}) => {
}
const initBucket = async () => {
- // TODO: Pagination?
- let response = await storageListBuckets({
- queries: ['limit(100)'],
- parseOutput: false
- })
+ const { buckets } = await paginate(storageListBuckets, { parseOutput: false }, 100, 'buckets');
- let buckets = response.buckets;
log(`Found ${buckets.length} buckets`);
buckets.forEach(async bucket => {
@@ -220,13 +222,8 @@ const initBucket = async () => {
}
const initTeam = async () => {
- // TODO: Pagination?
- let response = await teamsList({
- queries: ['limit(100)'],
- parseOutput: false
- })
+ const { teams } = await paginate(teamsList, { parseOutput: false }, 100, 'teams');
- let teams = response.teams;
log(`Found ${teams.length} teams`);
teams.forEach(async team => {
diff --git a/templates/cli/lib/config.js.twig b/templates/cli/lib/config.js.twig
index 744013e38..916865f44 100644
--- a/templates/cli/lib/config.js.twig
+++ b/templates/cli/lib/config.js.twig
@@ -15,7 +15,6 @@ class Config {
const file = fs.readFileSync(this.path).toString();
this.data = JSONbig.parse(file);
} catch (e) {
- // console.log(`${this.path} not found. Empty data`);
this.data = {};
}
}
@@ -25,7 +24,7 @@ class Config {
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
- fs.writeFileSync(this.path, JSONbig.stringify(this.data, null, 4));
+ fs.writeFileSync(this.path, JSONbig.stringify(this.data, null, 4), { mode: 0o600 });
}
get(key) {
diff --git a/templates/cli/lib/paginate.js.twig b/templates/cli/lib/paginate.js.twig
new file mode 100644
index 000000000..309e2ef0b
--- /dev/null
+++ b/templates/cli/lib/paginate.js.twig
@@ -0,0 +1,51 @@
+const paginate = async (action, args = {}, limit = 100, wrapper = '') => {
+ let pageNumber = 0;
+ let results = [];
+ let total = 0;
+
+ while (true) {
+ const offset = pageNumber * limit;
+
+ // Merge the limit and offset into the args
+ const response = await action({
+ ...args,
+ queries: [
+ `limit(${limit})`,
+ `offset(${offset})`
+ ]
+ });
+
+ if (wrapper === '') {
+ if (response.length === 0) {
+ break;
+ }
+ results = results.concat(response);
+ } else {
+ if (response[wrapper].length === 0) {
+ break;
+ }
+ results = results.concat(response[wrapper]);
+ }
+
+ total = response.total;
+
+ if (results.length >= total) {
+ break;
+ }
+
+ pageNumber++;
+ }
+
+ if (wrapper === '') {
+ return results;
+ }
+
+ return {
+ [wrapper]: results,
+ total,
+ };
+}
+
+module.exports = {
+ paginate
+};
\ No newline at end of file
diff --git a/templates/cli/lib/parser.js.twig b/templates/cli/lib/parser.js.twig
index a1634c3ed..ed6ba8d8c 100644
--- a/templates/cli/lib/parser.js.twig
+++ b/templates/cli/lib/parser.js.twig
@@ -169,6 +169,11 @@ const commandDescriptions = {
"login": `The login command allows you to authenticate and manage a user account.`,
"logout": `The logout command allows you to logout of your {{ spec.title|caseUcfirst }} account.`,
"console" : `The console command allows gives you access to the APIs used by the Appwrite console.`,
+ "assistant": `The assistant command allows you to interact with the Appwrite Assistant AI`,
+ "migrations": `The migrations command allows you to migrate data between services.`,
+ "project": `The project command is for overall project administration.`,
+ "proxy": `The proxy command allows you to configure behavior for your attached domains.`,
+ "vcs": `The vcs command allows you to interact with VCS providers and manage your code repositories.`,
"main": chalk.redBright(`${logo}${description}`),
{% if sdk.test == "true" %}
{% for service in spec.services %}
diff --git a/templates/cli/lib/questions.js.twig b/templates/cli/lib/questions.js.twig
index 0a8fb5857..0cb4540f3 100644
--- a/templates/cli/lib/questions.js.twig
+++ b/templates/cli/lib/questions.js.twig
@@ -20,6 +20,7 @@ const getIgnores = (runtime) => {
case 'kotlin':
return ['build'];
case 'node':
+ case 'bun':
return ['node_modules', '.npm'];
case 'php':
return ['vendor'];
@@ -43,27 +44,61 @@ const getEntrypoint = (runtime) => {
case 'dart':
return 'lib/main.dart';
case 'deno':
- return 'src/mod.ts';
+ return 'src/main.ts';
case 'node':
- return 'src/index.js';
+ return 'src/main.js';
+ case 'bun':
+ return 'src/main.ts';
case 'php':
return 'src/index.php';
case 'python':
- return 'src/index.py';
+ return 'src/main.py';
case 'ruby':
- return 'src/index.rb';
+ return 'lib/main.rb';
case 'rust':
return 'main.rs';
case 'swift':
- return 'Sources/swift-5.5/main.swift';
+ return 'Sources/index.swift';
case 'cpp':
- return 'src/index.cc';
+ return 'src/main.cc';
case 'dotnet':
return 'src/Index.cs';
case 'java':
- return 'src/Index.java';
+ return 'src/Main.java';
case 'kotlin':
- return 'src/Index.kt';
+ return 'src/Main.kt';
+ }
+
+ return undefined;
+};
+
+const getInstallCommand = (runtime) => {
+ const languge = runtime.split('-')[0];
+
+ switch (languge) {
+ case 'dart':
+ return 'dart pub get';
+ case 'deno':
+ return "deno install";
+ case 'node':
+ return 'npm install';
+ case 'bun':
+ return 'bun install';
+ case 'php':
+ return 'composer install';
+ case 'python':
+ return 'pip install -r requirements.txt';
+ case 'ruby':
+ return 'bundle install';
+ case 'rust':
+ return 'cargo install';
+ case 'dotnet':
+ return 'dotnet restore';
+ case 'swift':
+ case 'java':
+ case 'kotlin':
+ case 'cpp':
+ return '';
}
return undefined;
@@ -171,10 +206,15 @@ const questionsInitFunction = [
parseOutput: false
})
let runtimes = response["runtimes"]
- let choices = runtimes.map((runtime, idx) => {
+ let choices = runtimes.map((runtime, idx) => {
return {
name: `${runtime.name} (${runtime['$id']})`,
- value: { id: runtime['$id'], entrypoint: getEntrypoint(runtime['$id']), ignore: getIgnores(runtime['$id']) },
+ value: {
+ id: runtime['$id'],
+ entrypoint: getEntrypoint(runtime['$id']),
+ ignore: getIgnores(runtime['$id']),
+ commands : getInstallCommand(runtime['$id'])
+ },
}
})
return choices;
@@ -269,13 +309,12 @@ const questionsDeployCollections = [
if (collections.length === 0) {
throw new Error("No collections found in the current directory. Run `{{ language.params.executableName }} init collection` to fetch all your collections.");
}
- let choices = collections.map((collection, idx) => {
+ return collections.map(collection => {
return {
name: `${collection.name} (${collection['databaseId']} - ${collection['$id']})`,
value: `${collection['databaseId']}|${collection['$id']}`
}
- })
- return choices;
+ });
}
},
{
@@ -295,13 +334,12 @@ const questionsDeployBuckets = [
if (buckets.length === 0) {
throw new Error("No buckets found in the current directory. Run `appwrite init bucket` to fetch all your buckets.");
}
- let choices = buckets.map((bucket, idx) => {
+ return buckets.map(bucket => {
return {
name: `${bucket.name} (${bucket['$id']})`,
value: bucket.$id
}
- })
- return choices;
+ });
}
},
{
@@ -319,7 +357,7 @@ const questionsGetEntrypoint = [
default: null,
validate(value) {
if (!value) {
- return "Please enter your enrtypoint";
+ return "Please enter your entrypoint";
}
return true;
}
@@ -336,13 +374,12 @@ const questionsDeployTeams = [
if (teams.length === 0) {
throw new Error("No teams found in the current directory. Run `appwrite init team` to fetch all your teams.");
}
- let choices = teams.map((team, idx) => {
+ return teams.map(team => {
return {
name: `${team.name} (${team['$id']})`,
value: team.$id
}
- })
- return choices;
+ });
}
},
{
diff --git a/templates/cli/package.json.twig b/templates/cli/package.json.twig
index 0afdca6fc..b4c51eb16 100644
--- a/templates/cli/package.json.twig
+++ b/templates/cli/package.json.twig
@@ -22,7 +22,7 @@
"windows-arm64": "pkg -t node16-win-arm64 -o build/appwrite-cli-win-arm64.exe package.json"
},
"dependencies": {
- "axios": "^0.27.2",
+ "axios": "1.5.0",
"chalk": "4.1.2",
"cli-table3": "^0.6.2",
"commander": "^9.2.0",
diff --git a/templates/dart/base/requests/api.twig b/templates/dart/base/requests/api.twig
index 950658e81..43683fd6c 100644
--- a/templates/dart/base/requests/api.twig
+++ b/templates/dart/base/requests/api.twig
@@ -1,13 +1,13 @@
{% import 'dart/base/utils.twig' as utils %}
- final Map params = {
+ final Map apiParams = {
{{ utils.map_parameter(method.parameters.query) }}
{{ utils.map_parameter(method.parameters.body) }}
};
- final Map headers = {
+ final Map apiHeaders = {
{{ utils.map_headers(method.headers) }}
};
- final res = await client.call(HttpMethod.{{ method.method | caseLower }}, path: path, params: params, headers: headers);
+ final res = await client.call(HttpMethod.{{ method.method | caseLower }}, path: apiPath, params: apiParams, headers: apiHeaders);
return {% if method.responseModel and method.responseModel != 'any' %}models.{{method.responseModel | caseUcfirst | overrideIdentifier}}.fromMap(res.data){% else %} res.data{% endif %};
diff --git a/templates/dart/base/requests/file.twig b/templates/dart/base/requests/file.twig
index 97d3bcf1a..28c31c0a2 100644
--- a/templates/dart/base/requests/file.twig
+++ b/templates/dart/base/requests/file.twig
@@ -1,10 +1,10 @@
{% import 'dart/base/utils.twig' as utils %}
- final Map params = {
+ final Map apiParams = {
{{ utils.map_parameter(method.parameters.query) }}
{{ utils.map_parameter(method.parameters.body) }}
};
- final Map headers = {
+ final Map apiHeaders = {
{{ utils.map_headers(method.headers) }}
};
@@ -19,11 +19,11 @@
{% endif %}
{% endfor %}
final res = await client.chunkedUpload(
- path: path,
- params: params,
+ path: apiPath,
+ params: apiParams,
paramName: paramName,
idParamName: idParamName,
- headers: headers,
+ headers: apiHeaders,
onProgress: onProgress,
);
diff --git a/templates/dart/base/requests/location.twig b/templates/dart/base/requests/location.twig
index ab3e3dc4a..5e947787d 100644
--- a/templates/dart/base/requests/location.twig
+++ b/templates/dart/base/requests/location.twig
@@ -10,5 +10,5 @@
{% endif %}
};
- final res = await client.call(HttpMethod.{{ method.method | caseLower }}, path: path, params: params, responseType: ResponseType.bytes);
+ final res = await client.call(HttpMethod.{{ method.method | caseLower }}, path: apiPath, params: params, responseType: ResponseType.bytes);
return res.data;
\ No newline at end of file
diff --git a/templates/dart/base/requests/oauth.twig b/templates/dart/base/requests/oauth.twig
index 237079f36..fd82d84ac 100644
--- a/templates/dart/base/requests/oauth.twig
+++ b/templates/dart/base/requests/oauth.twig
@@ -27,7 +27,7 @@
Uri url = Uri(scheme: endpoint.scheme,
host: endpoint.host,
port: endpoint.port,
- path: endpoint.path + path,
+ path: endpoint.path + apiPath,
query: query.join('&')
);
diff --git a/templates/dart/lib/query.dart.twig b/templates/dart/lib/query.dart.twig
index 249a5f7db..a5763d79d 100644
--- a/templates/dart/lib/query.dart.twig
+++ b/templates/dart/lib/query.dart.twig
@@ -48,7 +48,7 @@ class Query {
/// Filter resources where [attribute] is between [start] and [end] (inclusive).
static String between(String attribute, dynamic start, dynamic end) =>
- _addQuery(attribute, 'between', [start, end]);
+ 'between("$attribute", ${_parseValues(start)}, ${_parseValues(end)})';
/// Filter resources where [attribute] starts with [value].
static String startsWith(String attribute, String value) =>
diff --git a/templates/dart/lib/role.dart.twig b/templates/dart/lib/role.dart.twig
index eea9e3c65..e4c968965 100644
--- a/templates/dart/lib/role.dart.twig
+++ b/templates/dart/lib/role.dart.twig
@@ -58,4 +58,9 @@ class Role {
static String member(String id) {
return 'member:$id';
}
+
+ /// Grants access to a user with the specified label.
+ static String label(String name) {
+ return 'label:$name';
+ }
}
\ No newline at end of file
diff --git a/templates/dart/lib/services/service.dart.twig b/templates/dart/lib/services/service.dart.twig
index 00141e5a3..71cba9ccc 100644
--- a/templates/dart/lib/services/service.dart.twig
+++ b/templates/dart/lib/services/service.dart.twig
@@ -20,7 +20,7 @@ class {{ service.name | caseUcfirst }} extends Service {
{{ method.description | dartComment }}
{% endif %}
{% if method.type == 'location' %}Future{% else %}{% if method.responseModel and method.responseModel != 'any' %}Future{% else %}Future{% endif %}{% endif %} {{ method.name | caseCamel }}({{ _self.method_parameters(method.parameters.all, method.consumes) }}) async {
- final String path = '{{ method.path }}'{% for parameter in method.parameters.path %}.replaceAll('{{ '{' }}{{ parameter.name | caseCamel }}{{ '}' }}', {{ parameter.name | caseCamel | overrideIdentifier }}){% endfor %};
+ final String apiPath = '{{ method.path }}'{% for parameter in method.parameters.path %}.replaceAll('{{ '{' }}{{ parameter.name | caseCamel }}{{ '}' }}', {{ parameter.name | caseCamel | overrideIdentifier }}){% endfor %};
{% if 'multipart/form-data' in method.consumes %}
{{ include('dart/base/requests/file.twig') }}
diff --git a/templates/dart/lib/src/client_browser.dart.twig b/templates/dart/lib/src/client_browser.dart.twig
index 221f7b4e1..c386af787 100644
--- a/templates/dart/lib/src/client_browser.dart.twig
+++ b/templates/dart/lib/src/client_browser.dart.twig
@@ -114,18 +114,18 @@ class ClientBrowser extends ClientBase with ClientMixin {
headers: headers,
);
final int chunksUploaded = res.data['chunksUploaded'] as int;
- offset = min(size, chunksUploaded * CHUNK_SIZE);
+ offset = chunksUploaded * CHUNK_SIZE;
} on {{spec.title | caseUcfirst}}Exception catch (_) {}
}
while (offset < size) {
- List chunk;
+ var chunk;
final end = min(offset + CHUNK_SIZE, size);
chunk = file.bytes!.getRange(offset, end).toList();
params[paramName] =
http.MultipartFile.fromBytes(paramName, chunk, filename: file.filename);
headers['content-range'] =
- 'bytes $offset-${min(((offset + CHUNK_SIZE) - 1), size)}/$size';
+ 'bytes $offset-${min((offset + CHUNK_SIZE - 1), size - 1)}/$size';
res = await call(HttpMethod.post,
path: path, headers: headers, params: params);
offset += CHUNK_SIZE;
@@ -134,8 +134,8 @@ class ClientBrowser extends ClientBase with ClientMixin {
}
final progress = UploadProgress(
$id: res.data['\$id'] ?? '',
- progress: min(offset - 1, size) / size * 100,
- sizeUploaded: min(offset - 1, size),
+ progress: min(offset, size) / size * 100,
+ sizeUploaded: min(offset, size),
chunksTotal: res.data['chunksTotal'] ?? 0,
chunksUploaded: res.data['chunksUploaded'] ?? 0,
);
diff --git a/templates/dart/lib/src/client_io.dart.twig b/templates/dart/lib/src/client_io.dart.twig
index d90ba15c0..bcacd2895 100644
--- a/templates/dart/lib/src/client_io.dart.twig
+++ b/templates/dart/lib/src/client_io.dart.twig
@@ -143,7 +143,7 @@ class ClientIO extends ClientBase with ClientMixin {
headers: headers,
);
final int chunksUploaded = res.data['chunksUploaded'] as int;
- offset = min(size, chunksUploaded * CHUNK_SIZE);
+ offset = chunksUploaded * CHUNK_SIZE;
} on {{spec.title | caseUcfirst}}Exception catch (_) {}
}
@@ -156,7 +156,7 @@ class ClientIO extends ClientBase with ClientMixin {
while (offset < size) {
List chunk = [];
if (file.bytes != null) {
- final end = min(offset + CHUNK_SIZE-1, size-1);
+ final end = min(offset + CHUNK_SIZE, size);
chunk = file.bytes!.getRange(offset, end).toList();
} else {
raf!.setPositionSync(offset);
@@ -165,7 +165,7 @@ class ClientIO extends ClientBase with ClientMixin {
params[paramName] =
http.MultipartFile.fromBytes(paramName, chunk, filename: file.filename);
headers['content-range'] =
- 'bytes $offset-${min(((offset + CHUNK_SIZE) - 1), size)}/$size';
+ 'bytes $offset-${min((offset + CHUNK_SIZE - 1), size - 1)}/$size';
res = await call(HttpMethod.post,
path: path, headers: headers, params: params);
offset += CHUNK_SIZE;
@@ -174,8 +174,8 @@ class ClientIO extends ClientBase with ClientMixin {
}
final progress = UploadProgress(
$id: res.data['\$id'] ?? '',
- progress: min(offset - 1, size) / size * 100,
- sizeUploaded: min(offset - 1, size),
+ progress: min(offset, size) / size * 100,
+ sizeUploaded: min(offset, size),
chunksTotal: res.data['chunksTotal'] ?? 0,
chunksUploaded: res.data['chunksUploaded'] ?? 0,
);
diff --git a/templates/dart/test/role_test.dart.twig b/templates/dart/test/role_test.dart.twig
index 1c06086b7..cc0fff4b4 100644
--- a/templates/dart/test/role_test.dart.twig
+++ b/templates/dart/test/role_test.dart.twig
@@ -53,4 +53,10 @@ void main() {
expect(Role.member('custom'), 'member:custom');
});
});
+
+ group('label()', () {
+ test('returns label', () {
+ expect(Role.label('admin'), 'label:admin');
+ });
+ });
}
diff --git a/templates/deno/src/exception.ts.twig b/templates/deno/src/exception.ts.twig
index 61b90cff5..aaa5e5f41 100644
--- a/templates/deno/src/exception.ts.twig
+++ b/templates/deno/src/exception.ts.twig
@@ -1,17 +1,17 @@
export class {{ spec.title | caseUcfirst}}Exception {
- message: String;
- code: Number;
+ message: string;
+ code: number;
response: any;
- type: String;
+ type: string;
- constructor(message: String, code: Number = 0, type: String = "", response: any = "") {
+ constructor(message: string, code: number = 0, type: string = "", response: any = "") {
this.message = message;
this.code = code;
this.type = type;
this.response = response;
}
- public toString(): String {
+ public toString(): string {
return `${this.message} - ${this.code} - ${this.type} - ${JSON.stringify(this.response)}`;
}
-}
\ No newline at end of file
+}
diff --git a/templates/deno/src/query.ts.twig b/templates/deno/src/query.ts.twig
index dd7c7a1cc..ce87185fb 100644
--- a/templates/deno/src/query.ts.twig
+++ b/templates/deno/src/query.ts.twig
@@ -31,7 +31,7 @@ export class Query {
`isNotNull("${attribute}")`;
static between = (attribute: string, start: string|number, end: string|number): string =>
- `between("${attribute}", [${Query.parseValues(start)},${Query.parseValues(end)}])`;
+ `between("${attribute}", ${Query.parseValues(start)}, ${Query.parseValues(end)})`;
static startsWith = (attribute: string, value: string): string =>
Query.addQuery(attribute, "startsWith", value);
diff --git a/templates/deno/src/role.ts.twig b/templates/deno/src/role.ts.twig
index d94e398c6..79f8c6b62 100644
--- a/templates/deno/src/role.ts.twig
+++ b/templates/deno/src/role.ts.twig
@@ -1,34 +1,100 @@
+/**
+ * Helper class to generate role strings for `Permission`.
+ */
export class Role {
+
+ /**
+ * Grants access to anyone.
+ *
+ * This includes authenticated and unauthenticated users.
+ *
+ * @returns {string}
+ */
public static any(): string {
return 'any'
}
+ /**
+ * Grants access to a specific user by user ID.
+ *
+ * You can optionally pass verified or unverified for
+ * `status` to target specific types of users.
+ *
+ * @param {string} id
+ * @param {string} status
+ * @returns {string}
+ */
public static user(id: string, status: string = ''): string {
- if(status === '') {
+ if (status === '') {
return `user:${id}`
}
return `user:${id}/${status}`
}
-
+
+ /**
+ * Grants access to any authenticated or anonymous user.
+ *
+ * You can optionally pass verified or unverified for
+ * `status` to target specific types of users.
+ *
+ * @param {string} status
+ * @returns {string}
+ */
public static users(status: string = ''): string {
- if(status === '') {
+ if (status === '') {
return 'users'
}
return `users/${status}`
}
-
+
+ /**
+ * Grants access to any guest user without a session.
+ *
+ * Authenticated users don't have access to this role.
+ *
+ * @returns {string}
+ */
public static guests(): string {
return 'guests'
}
-
+
+ /**
+ * Grants access to a team by team ID.
+ *
+ * You can optionally pass a role for `role` to target
+ * team members with the specified role.
+ *
+ * @param {string} id
+ * @param {string} role
+ * @returns {string}
+ */
public static team(id: string, role: string = ''): string {
- if(role === '') {
+ if (role === '') {
return `team:${id}`
}
return `team:${id}/${role}`
}
+ /**
+ * Grants access to a specific member of a team.
+ *
+ * When the member is removed from the team, they will
+ * no longer have access.
+ *
+ * @param {string} id
+ * @returns {string}
+ */
public static member(id: string): string {
return `member:${id}`
}
+
+ /**
+ * Grants access to a user with the specified label.
+ *
+ * @param {string} name
+ * @returns {string}
+ */
+ public static label(name: string): string {
+ return `label:${name}`
+ }
}
\ No newline at end of file
diff --git a/templates/deno/src/services/service.ts.twig b/templates/deno/src/services/service.ts.twig
index 8fdd11841..dbaecfca6 100644
--- a/templates/deno/src/services/service.ts.twig
+++ b/templates/deno/src/services/service.ts.twig
@@ -72,8 +72,8 @@ export class {{ service.name | caseUcfirst }} extends Service {
{% endif %}
{% endfor %}
- let path = '{{ method.path }}'{% for parameter in method.parameters.path %}.replace('{{ '{' }}{{ parameter.name }}{{ '}' }}', {{ parameter.name | caseCamel | escapeKeyword }}){% endfor %};
- let payload: Payload = {};
+ const apiPath = '{{ method.path }}'{% for parameter in method.parameters.path %}.replace('{{ '{' }}{{ parameter.name }}{{ '}' }}', {{ parameter.name | caseCamel | escapeKeyword }}){% endfor %};
+ const payload: Payload = {};
{% for parameter in method.parameters.query %}
if (typeof {{ parameter.name | caseCamel | escapeKeyword }} !== 'undefined') {
@@ -92,7 +92,7 @@ export class {{ service.name | caseUcfirst }} extends Service {
const size = {{ parameter.name | caseCamel | escapeKeyword }}.size;
- const headers: { [header: string]: string } = {
+ const apiHeaders: { [header: string]: string } = {
{% for parameter in method.parameters.header %}
'{{ parameter.name }}': ${{ parameter.name | caseCamel | escapeKeyword }},
{% endfor %}
@@ -110,7 +110,7 @@ export class {{ service.name | caseUcfirst }} extends Service {
{% if parameter.isUploadID %}
if({{ parameter.name | caseCamel | escapeKeyword }} != 'unique()') {
try {
- response = await this.client.call('get', path + '/' + {{ parameter.name }}, headers);
+ response = await this.client.call('get', apiPath + '/' + {{ parameter.name }}, apiHeaders);
chunksUploaded = response.chunksUploaded;
} catch(e) {
}
@@ -131,7 +131,7 @@ export class {{ service.name | caseUcfirst }} extends Service {
const end = start + currentPosition;
if(!lastUpload || currentChunk !== 1) {
- headers['content-range'] = 'bytes ' + start + '-' + end + '/' + size;
+ apiHeaders['content-range'] = 'bytes ' + start + '-' + end + '/' + size;
}
let uploadableChunkTrimmed: Uint8Array;
@@ -146,12 +146,12 @@ export class {{ service.name | caseUcfirst }} extends Service {
}
if (id) {
- headers['x-{{spec.title | caseLower }}-id'] = id;
+ apiHeaders['x-{{spec.title | caseLower }}-id'] = id;
}
payload['{{ parameter.name }}'] = { type: 'file', file: new File([uploadableChunkTrimmed], {{ parameter.name | caseCamel | escapeKeyword }}.filename), filename: {{ parameter.name | caseCamel | escapeKeyword }}.filename };
- response = await this.client.call('{{ method.method | caseLower }}', path, headers, payload{% if method.type == 'location' %}, 'arraybuffer'{% endif %});
+ response = await this.client.call('{{ method.method | caseLower }}', apiPath, apiHeaders, payload{% if method.type == 'location' %}, 'arraybuffer'{% endif %});
if (!id) {
id = response['$id'];
@@ -193,7 +193,7 @@ export class {{ service.name | caseUcfirst }} extends Service {
{% endif %}
{% endfor %}
{% else %}
- return await this.client.call('{{ method.method | caseLower }}', path, {
+ return await this.client.call('{{ method.method | caseLower }}', apiPath, {
{% for parameter in method.parameters.header %}
'{{ parameter.name }}': ${{ parameter.name | caseCamel | escapeKeyword }},
{% endfor %}
diff --git a/templates/deno/test/id.test.ts.twig b/templates/deno/test/id.test.ts.twig
new file mode 100644
index 000000000..6e7dcbed0
--- /dev/null
+++ b/templates/deno/test/id.test.ts.twig
@@ -0,0 +1,8 @@
+import {assertEquals} from "https://deno.land/std@0.204.0/assert/mod.ts";
+import {describe, it as test} from "https://deno.land/std@0.149.0/testing/bdd.ts";
+import {ID} from "../src/id.ts";
+
+describe("ID", () => {
+ test('unique', () => assertEquals(ID.unique(), 'unique()'));
+ test('custom', () => assertEquals(ID.custom('custom'), 'custom'));
+});
diff --git a/templates/deno/test/permission.test.ts.twig b/templates/deno/test/permission.test.ts.twig
new file mode 100644
index 000000000..a9366fd43
--- /dev/null
+++ b/templates/deno/test/permission.test.ts.twig
@@ -0,0 +1,12 @@
+import { assertEquals } from "https://deno.land/std@0.204.0/assert/mod.ts";
+import { describe, it as test } from "https://deno.land/std@0.149.0/testing/bdd.ts";
+import {Permission} from "../src/permission.ts";
+import {Role} from "../src/role.ts";
+
+describe('Permission', () => {
+ test('read', () => assertEquals(Permission.read(Role.any()), 'read("any")'));
+ test('write', () => assertEquals(Permission.write(Role.any()), 'write("any")'));
+ test('create', () => assertEquals(Permission.create(Role.any()), 'create("any")'));
+ test('update', () => assertEquals(Permission.update(Role.any()), 'update("any")'));
+ test('delete', () => assertEquals(Permission.delete(Role.any()), 'delete("any")'));
+})
diff --git a/templates/deno/test/query.test.ts.twig b/templates/deno/test/query.test.ts.twig
new file mode 100644
index 000000000..a307e55d2
--- /dev/null
+++ b/templates/deno/test/query.test.ts.twig
@@ -0,0 +1,175 @@
+import {describe, it as test} from "https://deno.land/std@0.149.0/testing/bdd.ts";
+import {assertEquals} from "https://deno.land/std@0.204.0/assert/assert_equals.ts";
+import {Query, QueryTypes} from "../src/query.ts";
+
+type BasicFilterQueryTest = {
+ description: string;
+ value: QueryTypes;
+ expectedValues: string;
+}
+
+const tests: BasicFilterQueryTest[] = [
+ {
+ description: 'with a string',
+ value: 's',
+ expectedValues: '["s"]'
+ },
+ {
+ description: 'with a integer',
+ value: 1,
+ expectedValues: '[1]'
+ },
+ {
+ description: 'with a double',
+ value: 1.2,
+ expectedValues: '[1.2]'
+ },
+ {
+ description: 'with a whole number double',
+ value: 1.0,
+ expectedValues: '[1]'
+ },
+ {
+ description: 'with a bool',
+ value: false,
+ expectedValues: '[false]'
+ },
+ {
+ description: 'with a list',
+ value: ['a', 'b', 'c'],
+ expectedValues: '["a","b","c"]'
+ }
+];
+
+describe('Query', () => {
+ describe('basic filter equal', () => {
+ for (const t of tests) {
+ test(t.description, () =>
+ assertEquals(
+ Query.equal("attr", t.value),
+ `equal("attr", ${t.expectedValues})`,
+ )
+ )
+ }
+ })
+
+ describe('basic filter notEqual', () => {
+ for (const t of tests) {
+ test(t.description, () =>
+ assertEquals(
+ Query.notEqual("attr", t.value),
+ `notEqual("attr", ${t.expectedValues})`,
+ )
+ )
+ }
+ });
+
+ describe('basic filter lessThan', () => {
+ for (const t of tests) {
+ test(t.description, () =>
+ assertEquals(
+ Query.lessThan("attr", t.value),
+ `lessThan("attr", ${t.expectedValues})`,
+ )
+ )
+ }
+ });
+
+ describe('basic filter lessThanEqual', () => {
+ for (const t of tests) {
+ test(t.description, () =>
+ assertEquals(
+ Query.lessThanEqual("attr", t.value),
+ `lessThanEqual("attr", ${t.expectedValues})`,
+ )
+ )
+ }
+ });
+
+ describe('basic filter greaterThan', () => {
+ for (const t of tests) {
+ test(t.description, () =>
+ assertEquals(
+ Query.greaterThan("attr", t.value),
+ `greaterThan("attr", ${t.expectedValues})`,
+ )
+ )
+ }
+ });
+
+ describe('basic filter greaterThanEqual', () => {
+ for (const t of tests) {
+ test(t.description, () =>
+ assertEquals(
+ Query.greaterThanEqual("attr", t.value),
+ `greaterThanEqual("attr", ${t.expectedValues})`,
+ )
+ )
+ }
+ });
+
+ test('search', () => assertEquals(
+ Query.search('attr', 'keyword1 keyword2'),
+ 'search("attr", ["keyword1 keyword2"])',
+ ));
+
+ test('isNull', () => assertEquals(
+ Query.isNull('attr'),
+ 'isNull("attr")',
+ ));
+
+ test('isNotNull', () => assertEquals(
+ Query.isNotNull('attr'),
+ 'isNotNull("attr")',
+ ));
+
+ describe('between', () => {
+ test('with integers', () => assertEquals(
+ Query.between('attr', 1, 2),
+ 'between("attr", 1, 2)'
+ ));
+ test('with doubles', () => assertEquals(
+ Query.between('attr', 1.2, 2.2),
+ 'between("attr", 1.2, 2.2)'
+ ));
+ test('with strings', () => assertEquals(
+ Query.between('attr', "a", "z"),
+ 'between("attr", "a", "z")'
+ ));
+ });
+
+ test('select', () => assertEquals(
+ Query.select(['attr1', 'attr2']),
+ 'select(["attr1","attr2"])',
+ ));
+
+ test('orderAsc', () => assertEquals(
+ Query.orderAsc('attr'),
+ 'orderAsc("attr")',
+ ));
+
+ test('orderDesc', () => assertEquals(
+ Query.orderDesc('attr'),
+ 'orderDesc("attr")',
+ ));
+
+ test('cursorBefore', () => assertEquals(
+ Query.cursorBefore('attr'),
+ 'cursorBefore("attr")',
+ ));
+
+ test('cursorAfter', () => assertEquals(
+ Query.cursorAfter('attr'),
+ 'cursorAfter("attr")',
+ ));
+
+ test('limit', () => assertEquals(
+ Query.limit(1),
+ 'limit(1)'
+ ));
+
+ test('offset', () => assertEquals(
+ Query.offset(1),
+ 'offset(1)'
+ ));
+})
diff --git a/templates/deno/test/role.test.ts.twig b/templates/deno/test/role.test.ts.twig
new file mode 100644
index 000000000..e3f155da0
--- /dev/null
+++ b/templates/deno/test/role.test.ts.twig
@@ -0,0 +1,16 @@
+import { assertEquals } from "https://deno.land/std@0.204.0/assert/mod.ts";
+import { describe, it as test } from "https://deno.land/std@0.149.0/testing/bdd.ts";
+import {Role} from "../src/role.ts";
+
+describe('Role', () => {
+ test('any', () => assertEquals(Role.any(), 'any'));
+ test('user without status', () => assertEquals(Role.user('custom'), 'user:custom'));
+ test('user with status', () => assertEquals(Role.user('custom', 'verified'), 'user:custom/verified'));
+ test('users without status', () => assertEquals(Role.users(), 'users'));
+ test('users with status', () => assertEquals(Role.users('verified'), 'users/verified'));
+ test('guests', () => assertEquals(Role.guests(), 'guests'));
+ test('team without role', () => assertEquals(Role.team('custom'), 'team:custom'))
+ test('team with role', () => assertEquals(Role.team('custom', 'owner'), 'team:custom/owner'))
+ test('member', () => assertEquals(Role.member('custom'), 'member:custom'))
+ test('label', () => assertEquals(Role.label('admin'), 'label:admin'))
+})
diff --git a/templates/deno/test/services/service.test.ts.twig b/templates/deno/test/services/service.test.ts.twig
new file mode 100644
index 000000000..00743c165
--- /dev/null
+++ b/templates/deno/test/services/service.test.ts.twig
@@ -0,0 +1,56 @@
+import {afterEach, describe, it as test} from "https://deno.land/std@0.204.0/testing/bdd.ts";
+import {restore, stub} from "https://deno.land/std@0.204.0/testing/mock.ts";
+import {assertEquals} from "https://deno.land/std@0.204.0/assert/assert_equals.ts";
+import { {{ service.name | caseUcfirst }} } from "../../src/services/{{ service.name | caseCamel }}.ts";
+import {Client} from "../../src/client.ts";
+import {InputFile} from "../../src/inputFile.ts"
+
+describe('{{ service.name | caseUcfirst }} service', () => {
+ const client = new Client();
+ const {{ service.name | caseCamel }} = new {{ service.name | caseUcfirst }}(client);
+
+ afterEach(() => restore())
+
+ {% for method in service.methods ~%}
+ test('test method {{ method.name | caseCamel }}()', async () => {
+ {%~ if method.type == 'webAuth' %}
+ const data = '';
+ {%~ elseif method.type == 'location' %}
+ const data = new Uint8Array(0);
+ {%~ else %}
+ {%- if method.responseModel and method.responseModel != 'any' %}
+ const data = {
+ {%- for definition in spec.definitions ~%}{%~ if definition.name == method.responseModel -%}{%~ for property in definition.properties | filter((param) => param.required) ~%}
+ '{{property.name | escapeDollarSign}}': {% if property.type == 'object' %}{}{% elseif property.type == 'array' %}[]{% elseif property.type == 'string' %}'{{property.example | escapeDollarSign}}'{% elseif property.type == 'boolean' %}true{% else %}{{property.example}}{% endif %},{%~ endfor ~%}{% set break = true %}{%- else -%}{% set continue = true %}{%- endif -%}{%~ endfor -%}
+ };
+ {%~ else %}
+ const data = '';
+ {%~ endif %}
+ {%~ endif %}
+
+ {%~ if method.type == 'location' %}
+ const stubbedFetch = stub(globalThis, 'fetch', () => Promise.resolve(new Response(data.buffer)));
+ {%~ elseif method.responseModel and method.responseModel != 'any' %}
+ const stubbedFetch = stub(globalThis, 'fetch', () => Promise.resolve(Response.json(data)));
+ {%~ else %}
+ const stubbedFetch = stub(globalThis, 'fetch', () => Promise.resolve(new Response(data)))
+ {%~ endif %}
+
+ const response = await {{ service.name | caseCamel }}.{{ method.name | caseCamel }}({%~ for parameter in method.parameters.all | filter((param) => param.required) ~%}
+ {% if parameter.type == 'object' %}{}{% elseif parameter.type == 'array' %}[]{% elseif parameter.type == 'file' %}InputFile.fromBuffer(new Uint8Array(0), 'image.png'){% elseif parameter.type == 'boolean' %}true{% elseif parameter.type == 'string' %}'{% if parameter.example is not empty %}{{parameter.example | escapeDollarSign}}{% endif %}'{% elseif parameter.type == 'integer' and parameter['x-example'] is empty %}1{% elseif parameter.type == 'number' and parameter['x-example'] is empty %}1.0{% else %}{{parameter.example}}{%~ endif ~%},{%~ endfor ~%}
+ );
+
+ {%~ if method.type == 'location' %}
+ const buffer = await response.arrayBuffer();
+ assertEquals(buffer.byteLength, 0);
+ {%~ elseif not method.responseModel or method.responseModel == 'any' %}
+ const text = await response.text();
+ assertEquals(text, data);
+ {%~ else %}
+ assertEquals(response, data);
+ {%~ endif %}
+ stubbedFetch.restore();
+ });
+
+ {% endfor %}
+})
diff --git a/templates/dotnet/README.md.twig b/templates/dotnet/README.md.twig
index 8315cd5fc..86b47f298 100644
--- a/templates/dotnet/README.md.twig
+++ b/templates/dotnet/README.md.twig
@@ -45,10 +45,31 @@ dotnet add package {{ spec.title | caseUcfirst }} --version {{ sdk.version }}
{{ sdk.gettingStarted|raw }}
{% endif %}
+### Preparing Models for Databases API
+
+For the .NET SDK, we use the `Newtonsoft.Json` library for serialization/deserialization support. The default behavior converts property names from `PascalCase` to `camelCase` on serializing to JSON. In case the names of attributes in your Appwrite collection are not created in `camelCase`, this serializer behavior can cause errors due to mismatches in the names in the serialized JSON and the actual attribute names in your collection.
+
+The way to fix this is to add the `JsonProperty` attribute to the properties in the POCO class you create for your model.
+
+For e.g., if you have two attributes, `name` (`string` type) and `release_date` (`DateTime` type), your POCO class would be created as follows:
+
+```csharp
+public class TestModel
+{
+ [JsonProperty("name")]
+ public string Name { get; set; }
+
+ [JsonProperty("release_date")]
+ public DateTime ReleaseDate { get; set; }
+}
+```
+
+The `JsonProperty` attribute will ensure that your data object for the Appwrite database is serialized with the correct names.
+
## Contribution
This library is auto-generated by Appwrite custom [SDK Generator](https://github.com/appwrite/sdk-generator). To learn more about how you can help us improve this SDK, please check the [contribution guide](https://github.com/appwrite/sdk-generator/blob/master/CONTRIBUTING.md) before sending a pull-request.
## License
-Please see the [{{spec.licenseName}} license]({{spec.licenseURL}}) file for more information.
\ No newline at end of file
+Please see the [{{spec.licenseName}} license]({{spec.licenseURL}}) file for more information.
diff --git a/templates/dotnet/base/params.twig b/templates/dotnet/base/params.twig
index 1ce2c6fd2..0918c9d65 100644
--- a/templates/dotnet/base/params.twig
+++ b/templates/dotnet/base/params.twig
@@ -4,7 +4,7 @@
{%~ endfor %}
- var parameters = new Dictionary()
+ var apiParameters = new Dictionary()
{
{%~ for parameter in method.parameters.query | merge(method.parameters.body) %}
{ "{{ parameter.name }}", {{ utils.map_parameter(parameter) }} }{% if not loop.last %},{% endif %}
@@ -12,7 +12,7 @@
{%~ endfor %}
};
- var headers = new Dictionary()
+ var apiHeaders = new Dictionary()
{
{%~ for key, header in method.headers %}
{ "{{ key }}", "{{ header }}" }{% if not loop.last %},{% endif %}
diff --git a/templates/dotnet/base/requests/api.twig b/templates/dotnet/base/requests/api.twig
index bf030e99c..0b10b627d 100644
--- a/templates/dotnet/base/requests/api.twig
+++ b/templates/dotnet/base/requests/api.twig
@@ -1,11 +1,11 @@
{% import 'dotnet/base/utils.twig' as utils %}
return _client.Call{% if method.type != 'webAuth' %}<{{ utils.resultType(spec.title, method) }}>{% endif %}(
method: "{{ method.method | caseUpper }}",
- path: path,
- headers: headers,
+ path: apiPath,
+ headers: apiHeaders,
{%~ if not method.responseModel %}
- parameters: parameters.Where(it => it.Value != null).ToDictionary(it => it.Key, it => it.Value)!);
+ parameters: apiParameters.Where(it => it.Value != null).ToDictionary(it => it.Key, it => it.Value)!);
{%~ else %}
- parameters: parameters.Where(it => it.Value != null).ToDictionary(it => it.Key, it => it.Value)!,
+ parameters: apiParameters.Where(it => it.Value != null).ToDictionary(it => it.Key, it => it.Value)!,
convert: Convert);
{%~ endif %}
\ No newline at end of file
diff --git a/templates/dotnet/base/requests/file.twig b/templates/dotnet/base/requests/file.twig
index 902b5043d..83bb3d3e7 100644
--- a/templates/dotnet/base/requests/file.twig
+++ b/templates/dotnet/base/requests/file.twig
@@ -7,9 +7,9 @@
{%~ endfor %}
return _client.ChunkedUpload(
- path,
- headers,
- parameters,
+ apiPath,
+ apiHeaders,
+ apiParameters,
{%~ if method.responseModel %}
Convert,
{%~ endif %}
diff --git a/templates/dotnet/base/requests/location.twig b/templates/dotnet/base/requests/location.twig
index 6b6b1301e..d9f25ea1c 100644
--- a/templates/dotnet/base/requests/location.twig
+++ b/templates/dotnet/base/requests/location.twig
@@ -1,5 +1,5 @@
return _client.Call(
method: "{{ method.method | caseUpper }}",
- path: path,
- headers: headers,
- parameters: parameters.Where(it => it.Value != null).ToDictionary(it => it.Key, it => it.Value)!);
\ No newline at end of file
+ path: apiPath,
+ headers: apiHeaders,
+ parameters: apiParameters.Where(it => it.Value != null).ToDictionary(it => it.Key, it => it.Value)!);
diff --git a/templates/dotnet/docs/example.md.twig b/templates/dotnet/docs/example.md.twig
index d90cde076..2ce25f701 100644
--- a/templates/dotnet/docs/example.md.twig
+++ b/templates/dotnet/docs/example.md.twig
@@ -1,4 +1,5 @@
using {{ spec.title | caseUcfirst }};
+using {{ spec.title | caseUcfirst }}.Services;
using {{ spec.title | caseUcfirst }}.Models;
var client = new Client()
diff --git a/templates/dotnet/icon.png b/templates/dotnet/icon.png
index 460b9f040..dadbae8ba 100644
Binary files a/templates/dotnet/icon.png and b/templates/dotnet/icon.png differ
diff --git a/templates/dotnet/src/Appwrite/Appwrite.csproj.twig b/templates/dotnet/src/Appwrite/Appwrite.csproj.twig
index fbb3d1b75..74673a1eb 100644
--- a/templates/dotnet/src/Appwrite/Appwrite.csproj.twig
+++ b/templates/dotnet/src/Appwrite/Appwrite.csproj.twig
@@ -9,6 +9,7 @@
{{sdk.shortDescription}}
icon.png
+ README.md
{{spec.licenseName}}
{{sdk.gitURL}}
git
@@ -22,6 +23,7 @@
+
diff --git a/templates/dotnet/src/Appwrite/Client.cs.twig b/templates/dotnet/src/Appwrite/Client.cs.twig
index 3692008d3..0f11ee45f 100644
--- a/templates/dotnet/src/Appwrite/Client.cs.twig
+++ b/templates/dotnet/src/Appwrite/Client.cs.twig
@@ -354,7 +354,7 @@ namespace {{ spec.title | caseUcfirst }}
parameters[paramName] = content;
headers["Content-Range"] =
- $"bytes {offset}-{Math.Min(offset + ChunkSize - 1, size)}/{size}";
+ $"bytes {offset}-{Math.Min(offset + ChunkSize - 1, size - 1)}/{size}";
result = await Call>(
method: "POST",
diff --git a/templates/dotnet/src/Appwrite/Exception.cs.twig b/templates/dotnet/src/Appwrite/Exception.cs.twig
index eb967f973..e78d78c2c 100644
--- a/templates/dotnet/src/Appwrite/Exception.cs.twig
+++ b/templates/dotnet/src/Appwrite/Exception.cs.twig
@@ -5,14 +5,17 @@ namespace {{spec.title | caseUcfirst}}
public class {{spec.title | caseUcfirst}}Exception : Exception
{
public int? Code { get; set; }
+ public string? Type { get; set; } = null;
public string? Response { get; set; } = null;
public {{spec.title | caseUcfirst}}Exception(
string? message = null,
int? code = null,
+ string? type = null,
string? response = null) : base(message)
{
this.Code = code;
+ this.Type = type;
this.Response = response;
}
public {{spec.title | caseUcfirst}}Exception(string message, Exception inner)
diff --git a/templates/dotnet/src/Appwrite/Models/Model.cs.twig b/templates/dotnet/src/Appwrite/Models/Model.cs.twig
index f032c2961..6e83a251a 100644
--- a/templates/dotnet/src/Appwrite/Models/Model.cs.twig
+++ b/templates/dotnet/src/Appwrite/Models/Model.cs.twig
@@ -40,7 +40,7 @@ namespace {{ spec.title | caseUcfirst }}.Models
public static {{ definition.name | caseUcfirst | overrideIdentifier}} From(Dictionary map) => new {{ definition.name | caseUcfirst | overrideIdentifier}}(
{%~ for property in definition.properties %}
- {{ property.name | caseCamel | escapeKeyword | removeDollarSign }}: {% if property.sub_schema %}{% if property.type == 'array' %}((JArray)map["{{ property.name }}"]).ToObject>>().Select(it => {{property.sub_schema | caseUcfirst | overrideIdentifier}}.From(map: it)).ToList(){% else %}{{property.sub_schema | caseUcfirst | overrideIdentifier}}.From(map: ((JObject)map["{{ property.name }}"]).ToObject>()!){% endif %}{% else %}{% if property.type == 'array' %}((JArray)map["{{ property.name }}"]).ToObject<{{ property | typeName }}>(){% else %}{% if property.type == "integer" or property.type == "number" %}{% if not property.required %}map["{{ property.name }}"] == null ? null : {% endif %}Convert.To{% if property.type == "integer" %}Int64{% else %}Double{% endif %}(map["{{ property.name }}"]){% else %}{% if property.type == "bool" %}({{ property | typeName }}{% if not property.required %}?{% endif %})map["{{ property.name }}"]{% endif %}map["{{ property.name }}"]{% if not property.required %}?{% endif %}.ToString(){% endif %}{% endif %}{% endif %}{% if not loop.last or (loop.last and definition.additionalProperties) %},{% endif %}
+ {{ property.name | caseCamel | escapeKeyword | removeDollarSign }}: {% if property.sub_schema %}{% if property.type == 'array' %}((JArray)map["{{ property.name }}"]).ToObject>>().Select(it => {{property.sub_schema | caseUcfirst | overrideIdentifier}}.From(map: it)).ToList(){% else %}{{property.sub_schema | caseUcfirst | overrideIdentifier}}.From(map: ((JObject)map["{{ property.name }}"]).ToObject>()!){% endif %}{% else %}{% if property.type == 'array' %}((JArray)map["{{ property.name }}"]).ToObject<{{ property | typeName }}>(){% else %}{% if property.type == "integer" or property.type == "number" %}{% if not property.required %}map["{{ property.name }}"] == null ? null : {% endif %}Convert.To{% if property.type == "integer" %}Int64{% else %}Double{% endif %}(map["{{ property.name }}"]){% else %}{% if property.type == "boolean" %}({{ property | typeName }}{% if not property.required %}?{% endif %})map["{{ property.name }}"]{% else %}map["{{ property.name }}"]{% if not property.required %}?{% endif %}.ToString(){% endif %}{% endif %}{% endif %}{% endif %}{% if not loop.last or (loop.last and definition.additionalProperties) %},{% endif %}
{%~ endfor %}
{%~ if definition.additionalProperties %}
diff --git a/templates/dotnet/src/Appwrite/Query.cs.twig b/templates/dotnet/src/Appwrite/Query.cs.twig
index c940f4d6b..f10bac28c 100644
--- a/templates/dotnet/src/Appwrite/Query.cs.twig
+++ b/templates/dotnet/src/Appwrite/Query.cs.twig
@@ -63,17 +63,17 @@ namespace Appwrite
public static string Between(string attribute, string start, string end)
{
- return AddQuery(attribute, "between", new List { start, end });
+ return $"between(\"{attribute}\", \"{start}\", \"{end}\")";
}
public static string Between(string attribute, int start, int end)
{
- return AddQuery(attribute, "between", new List { start, end });
+ return $"between(\"{attribute}\", {start}, {end})";
}
public static string Between(string attribute, double start, double end)
{
- return AddQuery(attribute, "between", new List { start, end });
+ return $"between(\"{attribute}\", {start}, {end})";
}
public static string Select(List attributes)
diff --git a/templates/dotnet/src/Appwrite/Role.cs.twig b/templates/dotnet/src/Appwrite/Role.cs.twig
index 7fc1fdbcd..b3ecf2610 100644
--- a/templates/dotnet/src/Appwrite/Role.cs.twig
+++ b/templates/dotnet/src/Appwrite/Role.cs.twig
@@ -1,12 +1,28 @@
namespace Appwrite
{
+ ///
+ /// Helper class to generate role strings for Permission.
+ ///
public static class Role
{
+ ///
+ /// Grants access to anyone.
+ ///
+ /// This includes authenticated and unauthenticated users.
+ ///
+ ///
public static string Any()
{
return "any";
}
+ ///
+ /// Grants access to a specific user by user ID.
+ ///
+ /// You can optionally pass verified or unverified for
+ /// status to target specific types of users.
+ ///
+ ///
public static string User(string id, string status = "")
{
return status == string.Empty
@@ -14,6 +30,13 @@ namespace Appwrite
: $"user:{id}/{status}";
}
+ ///
+ /// Grants access to any authenticated or anonymous user.
+ ///
+ /// You can optionally pass verified or unverified for
+ /// status to target specific types of users.
+ ///
+ ///
public static string Users(string status = "")
{
return status == string.Empty
@@ -21,11 +44,24 @@ namespace Appwrite
$"users/{status}";
}
+ ///
+ /// Grants access to any guest user without a session.
+ ///
+ /// Authenticated users don't have access to this role.
+ ///
+ ///
public static string Guests()
{
return "guests";
}
+ ///
+ /// Grants access to a team by team ID.
+ ///
+ /// You can optionally pass a role for role to target
+ /// team members with the specified role.
+ ///
+ ///
public static string Team(string id, string role = "")
{
return role == string.Empty
@@ -33,9 +69,24 @@ namespace Appwrite
: $"team:{id}/{role}";
}
+ ///
+ /// Grants access to a specific member of a team.
+ ///
+ /// When the member is removed from the team, they will
+ /// no longer have access.
+ ///
+ ///
public static string Member(string id)
{
return $"member:{id}";
}
+
+ ///
+ /// Grants access to a user with the specified label.
+ ///
+ public static string Label(string name)
+ {
+ return $"label:{name}";
+ }
}
}
\ No newline at end of file
diff --git a/templates/dotnet/src/Appwrite/Services/ServiceTemplate.cs.twig b/templates/dotnet/src/Appwrite/Services/ServiceTemplate.cs.twig
index a7711ec93..732d36ac7 100644
--- a/templates/dotnet/src/Appwrite/Services/ServiceTemplate.cs.twig
+++ b/templates/dotnet/src/Appwrite/Services/ServiceTemplate.cs.twig
@@ -27,7 +27,7 @@ namespace {{ spec.title | caseUcfirst }}.Services
///
public Task{% if method.type != "webAuth" %}<{{ utils.resultType(spec.title, method) }}>{% endif %} {{ method.name | caseUcfirst }}({{ utils.method_parameters(method.parameters, method.consumes) }})
{
- var path = "{{ method.path }}"{% if method.parameters.path | length == 0 %};{% endif %}
+ var apiPath = "{{ method.path }}"{% if method.parameters.path | length == 0 %};{% endif %}
{{~ include('dotnet/base/params.twig') }}
diff --git a/templates/flutter/base/requests/api.twig b/templates/flutter/base/requests/api.twig
index 233b53490..512372d45 100644
--- a/templates/flutter/base/requests/api.twig
+++ b/templates/flutter/base/requests/api.twig
@@ -1,13 +1,13 @@
{% import 'flutter/base/utils.twig' as utils %}
- final Map params = {
+ final Map apiParams = {
{{- utils.map_parameter(method.parameters.query) -}}
{{~ utils.map_parameter(method.parameters.body) }}
};
- final Map headers = {
+ final Map apiHeaders = {
{{~ utils.map_headers(method.headers) }}
};
- final res = await client.call(HttpMethod.{{ method.method | caseLower }}, path: path, params: params, headers: headers);
+ final res = await client.call(HttpMethod.{{ method.method | caseLower }}, path: apiPath, params: apiParams, headers: apiHeaders);
return {% if method.responseModel and method.responseModel != 'any' %}models.{{method.responseModel | caseUcfirst | overrideIdentifier}}.fromMap(res.data){% else %} res.data{% endif %};
diff --git a/templates/flutter/base/requests/file.twig b/templates/flutter/base/requests/file.twig
index 7a5777115..3030fb494 100644
--- a/templates/flutter/base/requests/file.twig
+++ b/templates/flutter/base/requests/file.twig
@@ -1,10 +1,10 @@
{% import 'flutter/base/utils.twig' as utils %}
- final Map params = {
+ final Map apiParams = {
{{~ utils.map_parameter(method.parameters.query) }}
{{~ utils.map_parameter(method.parameters.body) }}
};
- final Map headers = {
+ final Map apiHeaders = {
{{~ utils.map_headers(method.headers) }}
};
@@ -19,11 +19,11 @@
{% endif %}
{% endfor %}
final res = await client.chunkedUpload(
- path: path,
- params: params,
+ path: apiPath,
+ params: apiParams,
paramName: paramName,
idParamName: idParamName,
- headers: headers,
+ headers: apiHeaders,
onProgress: onProgress,
);
diff --git a/templates/flutter/base/requests/location.twig b/templates/flutter/base/requests/location.twig
index 76be6e723..1135c3cad 100644
--- a/templates/flutter/base/requests/location.twig
+++ b/templates/flutter/base/requests/location.twig
@@ -10,5 +10,5 @@
{% endif %}
};
- final res = await client.call(HttpMethod.{{ method.method | caseLower }}, path: path, params: params, responseType: ResponseType.bytes);
+ final res = await client.call(HttpMethod.{{ method.method | caseLower }}, path: apiPath, params: params, responseType: ResponseType.bytes);
return res.data;
\ No newline at end of file
diff --git a/templates/flutter/base/requests/oauth.twig b/templates/flutter/base/requests/oauth.twig
index 36538cd3d..4c5617bd9 100644
--- a/templates/flutter/base/requests/oauth.twig
+++ b/templates/flutter/base/requests/oauth.twig
@@ -27,7 +27,7 @@
Uri url = Uri(scheme: endpoint.scheme,
host: endpoint.host,
port: endpoint.port,
- path: endpoint.path + path,
+ path: endpoint.path + apiPath,
query: query.join('&')
);
diff --git a/templates/flutter/lib/services/service.dart.twig b/templates/flutter/lib/services/service.dart.twig
index 0cc609d03..03be8528e 100644
--- a/templates/flutter/lib/services/service.dart.twig
+++ b/templates/flutter/lib/services/service.dart.twig
@@ -21,7 +21,7 @@ class {{ service.name | caseUcfirst }} extends Service {
{{ method.description|dartComment }}
{% endif %}
{% if method.type == 'webAuth' %}Future{% elseif method.type == 'location' %}Future{% else %}{% if method.responseModel and method.responseModel != 'any' %}Future{% else %}Future{% endif %}{% endif %} {{ method.name | caseCamel }}({{ _self.method_parameters(method.parameters.all, method.consumes) }}) async {
- {% if method.parameters.path | length > 0 %}final{% else %}const{% endif %} String path = '{{ method.path }}'{% for parameter in method.parameters.path %}.replaceAll('{{ '{' }}{{ parameter.name | caseCamel }}{{ '}' }}', {{ parameter.name | caseCamel | overrideIdentifier }}){% endfor %};
+ {% if method.parameters.path | length > 0 %}final{% else %}const{% endif %} String apiPath = '{{ method.path }}'{% for parameter in method.parameters.path %}.replaceAll('{{ '{' }}{{ parameter.name | caseCamel }}{{ '}' }}', {{ parameter.name | caseCamel | overrideIdentifier }}){% endfor %};
{% if 'multipart/form-data' in method.consumes %}
{{ include('flutter/base/requests/file.twig') }}
diff --git a/templates/flutter/lib/src/client_browser.dart.twig b/templates/flutter/lib/src/client_browser.dart.twig
index 948915c5f..52132bb9f 100644
--- a/templates/flutter/lib/src/client_browser.dart.twig
+++ b/templates/flutter/lib/src/client_browser.dart.twig
@@ -143,7 +143,7 @@ class ClientBrowser extends ClientBase with ClientMixin {
headers: headers,
);
final int chunksUploaded = res.data['chunksUploaded'] as int;
- offset = min(size, chunksUploaded * CHUNK_SIZE);
+ offset = chunksUploaded * CHUNK_SIZE;
} on {{spec.title | caseUcfirst}}Exception catch (_) {}
}
@@ -154,7 +154,7 @@ class ClientBrowser extends ClientBase with ClientMixin {
params[paramName] =
http.MultipartFile.fromBytes(paramName, chunk, filename: file.filename);
headers['content-range'] =
- 'bytes $offset-${min(((offset + CHUNK_SIZE) - 1), size)}/$size';
+ 'bytes $offset-${min((offset + CHUNK_SIZE - 1), size - 1)}/$size';
res = await call(HttpMethod.post,
path: path, headers: headers, params: params);
offset += CHUNK_SIZE;
@@ -163,8 +163,8 @@ class ClientBrowser extends ClientBase with ClientMixin {
}
final progress = UploadProgress(
$id: res.data['\$id'] ?? '',
- progress: min(offset - 1, size) / size * 100,
- sizeUploaded: min(offset - 1, size),
+ progress: min(offset, size) / size * 100,
+ sizeUploaded: min(offset, size),
chunksTotal: res.data['chunksTotal'] ?? 0,
chunksUploaded: res.data['chunksUploaded'] ?? 0,
);
diff --git a/templates/flutter/lib/src/client_io.dart.twig b/templates/flutter/lib/src/client_io.dart.twig
index a200fcb49..371dd51ee 100644
--- a/templates/flutter/lib/src/client_io.dart.twig
+++ b/templates/flutter/lib/src/client_io.dart.twig
@@ -263,7 +263,7 @@ class ClientIO extends ClientBase with ClientMixin {
headers: headers,
);
final int chunksUploaded = res.data['chunksUploaded'] as int;
- offset = min(size, chunksUploaded * CHUNK_SIZE);
+ offset = chunksUploaded * CHUNK_SIZE;
} on {{spec.title | caseUcfirst}}Exception catch (_) {}
}
@@ -276,7 +276,7 @@ class ClientIO extends ClientBase with ClientMixin {
while (offset < size) {
List chunk = [];
if (file.bytes != null) {
- final end = min(offset + CHUNK_SIZE-1, size-1);
+ final end = min(offset + CHUNK_SIZE, size);
chunk = file.bytes!.getRange(offset, end).toList();
} else {
raf!.setPositionSync(offset);
@@ -285,7 +285,7 @@ class ClientIO extends ClientBase with ClientMixin {
params[paramName] =
http.MultipartFile.fromBytes(paramName, chunk, filename: file.filename);
headers['content-range'] =
- 'bytes $offset-${min(((offset + CHUNK_SIZE) - 1), size)}/$size';
+ 'bytes $offset-${min((offset + CHUNK_SIZE - 1), size - 1)}/$size';
res = await call(HttpMethod.post,
path: path, headers: headers, params: params);
offset += CHUNK_SIZE;
@@ -294,8 +294,8 @@ class ClientIO extends ClientBase with ClientMixin {
}
final progress = UploadProgress(
$id: res.data['\$id'] ?? '',
- progress: min(offset - 1, size) / size * 100,
- sizeUploaded: min(offset - 1, size),
+ progress: min(offset, size) / size * 100,
+ sizeUploaded: min(offset, size),
chunksTotal: res.data['chunksTotal'] ?? 0,
chunksUploaded: res.data['chunksUploaded'] ?? 0,
);
@@ -314,7 +314,9 @@ class ClientIO extends ClientBase with ClientMixin {
callbackUrlScheme: callbackUrlScheme != null && _customSchemeAllowed
? callbackUrlScheme
: "{{spec.title | caseLower}}-callback-" + config['project']!,
- preferEphemeral: true,
+ options: const FlutterWebAuth2Options(
+ intentFlags: ephemeralIntentFlags,
+ ),
).then((value) async {
Uri url = Uri.parse(value);
final key = url.queryParameters['key'];
diff --git a/templates/flutter/pubspec.yaml.twig b/templates/flutter/pubspec.yaml.twig
index c2463e971..5b0137296 100644
--- a/templates/flutter/pubspec.yaml.twig
+++ b/templates/flutter/pubspec.yaml.twig
@@ -13,15 +13,15 @@ platforms:
web:
windows:
environment:
- sdk: '>=2.17.0 <3.0.0'
+ sdk: '>=2.17.0 <4.0.0'
dependencies:
flutter:
sdk: flutter
cookie_jar: ^4.0.3
device_info_plus: ^9.0.2
- flutter_web_auth_2: ^2.1.4
- http: '>=0.13.6 <1.0.1'
+ flutter_web_auth_2: ^3.0.0
+ http: '>=0.13.6 <2.0.0'
package_info_plus: ^4.0.2
path_provider: ^2.0.15
web_socket_channel: ^2.4.0
@@ -32,4 +32,4 @@ dev_dependencies:
flutter_lints: ^2.0.1
flutter_test:
sdk: flutter
- mockito: ^5.4.0
\ No newline at end of file
+ mockito: ^5.4.0
diff --git a/templates/go/base/params.twig b/templates/go/base/params.twig
index cc62c584b..34cbd8b51 100644
--- a/templates/go/base/params.twig
+++ b/templates/go/base/params.twig
@@ -1,4 +1,4 @@
- params := map[string]interface{}{
+ apiParams := map[string]interface{}{
{% for parameter in method.parameters.query %}
"{{ parameter.name }}": {{ parameter.name | caseUcfirst }},
{% endfor %}
@@ -7,7 +7,7 @@
{% endfor %}
}
- headers := map[string]interface{}{
+ apiHeaders := map[string]interface{}{
{% for key, header in method.headers %}
"{{ key }}": "{{ header }}",
{% endfor %}
diff --git a/templates/go/base/requests/api.twig b/templates/go/base/requests/api.twig
index 034a83680..2b0ec67bb 100644
--- a/templates/go/base/requests/api.twig
+++ b/templates/go/base/requests/api.twig
@@ -1 +1 @@
- return srv.client.Call("{{ method.method | caseUpper }}", path, headers, params)
\ No newline at end of file
+ return srv.client.Call("{{ method.method | caseUpper }}", apiPath, apiHeaders, apiParams)
\ No newline at end of file
diff --git a/templates/go/services/service.go.twig b/templates/go/services/service.go.twig
index 2ee676ef0..33a91dc86 100644
--- a/templates/go/services/service.go.twig
+++ b/templates/go/services/service.go.twig
@@ -29,10 +29,10 @@ func New{{ service.name | caseUcfirst }}(clt Client) *{{ service.name | caseUcfi
func (srv *{{ service.name | caseUcfirst }}) {{ method.name | caseUcfirst }}({% if method.parameters.all|length > 0 %}{% for parameter in method.parameters.all %}{{ parameter.name | caseUcfirst }} {{ parameter | typeName }}{% if not loop.last %}, {% endif %}{% endfor %}{% endif %}) (*ClientResponse, error) {
{% if method.parameters.path|length > 0 %}
r := strings.NewReplacer({% for parameter in method.parameters.path %}"{{ '{' }}{{ parameter.name | caseCamel }}{{ '}' }}", {{ parameter.name | caseUcfirst }}{% if not loop.last %}, {% endif %}{% endfor %})
- path := r.Replace("{{ method.path }}")
+ apiPath := r.Replace("{{ method.path }}")
{% else %}
- path := "{{ method.path }}"
+ apiPath := "{{ method.path }}"
{% endif %}
{{include('go/base/params.twig')}}
diff --git a/templates/kotlin/base/requests/api.twig b/templates/kotlin/base/requests/api.twig
index a823ba506..2b00f48a9 100644
--- a/templates/kotlin/base/requests/api.twig
+++ b/templates/kotlin/base/requests/api.twig
@@ -1,8 +1,8 @@
return client.call(
"{{ method.method | caseUpper }}",
- path,
- headers,
- params,
+ apiPath,
+ apiHeaders,
+ apiParams,
{%~ if method.responseModel | hasGenericType(spec) %}
responseType = classOf(),
{%~ else %}
diff --git a/templates/kotlin/base/requests/file.twig b/templates/kotlin/base/requests/file.twig
index 4da9ef84d..a1b7c0f85 100644
--- a/templates/kotlin/base/requests/file.twig
+++ b/templates/kotlin/base/requests/file.twig
@@ -6,9 +6,9 @@
{%~ endif %}
{%~ endfor %}
return client.chunkedUpload(
- path,
- headers,
- params,
+ apiPath,
+ apiHeaders,
+ apiParams,
responseType = {{ method | returnType(spec, sdk.namespace | caseDot) | raw }}::class.java,
{%~ if method.responseModel %}
converter,
diff --git a/templates/kotlin/base/requests/location.twig b/templates/kotlin/base/requests/location.twig
index df3f67171..e8d5de57f 100644
--- a/templates/kotlin/base/requests/location.twig
+++ b/templates/kotlin/base/requests/location.twig
@@ -1,6 +1,6 @@
return client.call(
"{{ method.method | caseUpper }}",
- path,
- params = params,
+ apiPath,
+ params = apiParams,
responseType = {{ method | returnType(spec, sdk.namespace | caseDot) | raw }}::class.java
)
\ No newline at end of file
diff --git a/templates/kotlin/src/main/kotlin/io/appwrite/Client.kt.twig b/templates/kotlin/src/main/kotlin/io/appwrite/Client.kt.twig
index 3c12978d5..7181175ca 100644
--- a/templates/kotlin/src/main/kotlin/io/appwrite/Client.kt.twig
+++ b/templates/kotlin/src/main/kotlin/io/appwrite/Client.kt.twig
@@ -333,7 +333,7 @@ class Client @JvmOverloads constructor(
responseType = Map::class.java,
)
val chunksUploaded = current["chunksUploaded"] as Long
- offset = (chunksUploaded * CHUNK_SIZE).coerceAtMost(size)
+ offset = chunksUploaded * CHUNK_SIZE
}
while (offset < size) {
@@ -344,7 +344,7 @@ class Client @JvmOverloads constructor(
}
"bytes" -> {
val end = if (offset + CHUNK_SIZE < size) {
- offset + CHUNK_SIZE
+ offset + CHUNK_SIZE - 1
} else {
size - 1
}
@@ -364,7 +364,7 @@ class Client @JvmOverloads constructor(
)
headers["Content-Range"] =
- "bytes $offset-${((offset + CHUNK_SIZE) - 1).coerceAtMost(size)}/$size"
+ "bytes $offset-${((offset + CHUNK_SIZE) - 1).coerceAtMost(size - 1)}/$size"
result = call(
method = "POST",
diff --git a/templates/kotlin/src/main/kotlin/io/appwrite/Query.kt.twig b/templates/kotlin/src/main/kotlin/io/appwrite/Query.kt.twig
index e9c5a9c88..7f4fbcd81 100644
--- a/templates/kotlin/src/main/kotlin/io/appwrite/Query.kt.twig
+++ b/templates/kotlin/src/main/kotlin/io/appwrite/Query.kt.twig
@@ -20,11 +20,11 @@ class Query {
fun isNotNull(attribute: String) = "isNotNull(\"${attribute}\")"
- fun between(attribute: String, start: Int, end: Int) = Query.addQuery(attribute, "between", listOf(start, end))
+ fun between(attribute: String, start: Int, end: Int) = "between(\"${attribute}\", ${start}, ${end})"
- fun between(attribute: String, start: Double, end: Double) = Query.addQuery(attribute, "between", listOf(start, end))
+ fun between(attribute: String, start: Double, end: Double) = "between(\"${attribute}\", ${start}, ${end})"
- fun between(attribute: String, start: String, end: String) = Query.addQuery(attribute, "between", listOf(start, end))
+ fun between(attribute: String, start: String, end: String) = "between(\"${attribute}\", \"${start}\", \"${end}\")"
fun startsWith(attribute: String, value: String) = Query.addQuery(attribute, "startsWith", value)
diff --git a/templates/kotlin/src/main/kotlin/io/appwrite/Role.kt.twig b/templates/kotlin/src/main/kotlin/io/appwrite/Role.kt.twig
index d5f59c3a6..2e4de9861 100644
--- a/templates/kotlin/src/main/kotlin/io/appwrite/Role.kt.twig
+++ b/templates/kotlin/src/main/kotlin/io/appwrite/Role.kt.twig
@@ -1,29 +1,72 @@
package {{ sdk.namespace | caseDot }}
+/**
+ * Helper class to generate role strings for [Permission].
+ */
class Role {
companion object {
+
+ /**
+ * Grants access to anyone.
+ *
+ * This includes authenticated and unauthenticated users.
+ */
fun any(): String = "any"
+ /**
+ * Grants access to a specific user by user ID.
+ *
+ * You can optionally pass verified or unverified for
+ * [status] to target specific types of users.
+ */
fun user(id: String, status: String = ""): String = if(status.isEmpty()) {
"user:$id"
} else {
"user:$id/$status"
}
+ /**
+ * Grants access to any authenticated or anonymous user.
+ *
+ * You can optionally pass verified or unverified for
+ * [status] to target specific types of users.
+ */
fun users(status: String = ""): String = if(status.isEmpty()) {
"users"
} else {
"users/$status"
}
+ /**
+ * Grants access to any guest user without a session.
+ *
+ * Authenticated users don't have access to this role.
+ */
fun guests(): String = "guests"
+ /**
+ * Grants access to a team by team ID.
+ *
+ * You can optionally pass a role for [role] to target
+ * team members with the specified role.
+ */
fun team(id: String, role: String = ""): String = if(role.isEmpty()) {
"team:$id"
} else {
"team:$id/$role"
}
+ /**
+ * Grants access to a specific member of a team.
+ *
+ * When the member is removed from the team, they will
+ * no longer have access.
+ */
fun member(id: String): String = "member:$id"
+
+ /**
+ * Grants access to a user with the specified label.
+ */
+ fun label(name: String): String = "label:$name"
}
}
\ No newline at end of file
diff --git a/templates/kotlin/src/main/kotlin/io/appwrite/services/ServiceTemplate.kt.twig b/templates/kotlin/src/main/kotlin/io/appwrite/services/ServiceTemplate.kt.twig
index 682752960..5fb0f8069 100644
--- a/templates/kotlin/src/main/kotlin/io/appwrite/services/ServiceTemplate.kt.twig
+++ b/templates/kotlin/src/main/kotlin/io/appwrite/services/ServiceTemplate.kt.twig
@@ -49,12 +49,12 @@ class {{ service.name | caseUcfirst }} : Service {
onProgress: ((UploadProgress) -> Unit)? = null
{%~ endif %}
): {{ method | returnType(spec, sdk.namespace | caseDot) | raw }} {
- val path = "{{ method.path }}"
+ val apiPath = "{{ method.path }}"
{%~ for parameter in method.parameters.path %}
.replace("{{ '{' ~ parameter.name | caseCamel ~ '}' }}", {{ parameter.name | caseCamel }})
{%~ endfor %}
- val params = mutableMapOf(
+ val apiParams = mutableMapOf(
{%~ for parameter in method.parameters.query | merge(method.parameters.body) %}
"{{ parameter.name }}" to {{ parameter.name | caseCamel }},
{%~ endfor %}
@@ -62,7 +62,7 @@ class {{ service.name | caseUcfirst }} : Service {
{%~ if method.type == 'location' %}
{{~ include('kotlin/base/requests/location.twig') }}
{%~ else %}
- val headers = mutableMapOf(
+ val apiHeaders = mutableMapOf(
{%~ for key, header in method.headers %}
"{{ key }}" to "{{ header }}",
{%~ endfor %}
diff --git a/templates/node/base/requests/api.twig b/templates/node/base/requests/api.twig
index 37ece0471..4c578a889 100644
--- a/templates/node/base/requests/api.twig
+++ b/templates/node/base/requests/api.twig
@@ -1,4 +1,4 @@
- return await this.client.call('{{ method.method | caseLower }}', path, {
+ return await this.client.call('{{ method.method | caseLower }}', apiPath, {
{% for parameter in method.parameters.header %}
'{{ parameter.name }}': ${{ parameter.name | caseCamel | escapeKeyword }},
{% endfor %}
diff --git a/templates/node/base/requests/file.twig b/templates/node/base/requests/file.twig
index 533dc5d5b..1f6f4a85b 100644
--- a/templates/node/base/requests/file.twig
+++ b/templates/node/base/requests/file.twig
@@ -2,7 +2,7 @@
{% if parameter.type == 'file' %}
const size = {{ parameter.name | caseCamel | escapeKeyword }}.size;
- const headers = {
+ const apiHeaders = {
{% for parameter in method.parameters.header %}
'{{ parameter.name }}': ${{ parameter.name | caseCamel | escapeKeyword }},
{% endfor %}
@@ -20,7 +20,7 @@
{% if parameter.isUploadID %}
if({{ parameter.name | caseCamel | escapeKeyword }} != 'unique()') {
try {
- response = await this.client.call('get', path + '/' + {{ parameter.name }}, headers);
+ response = await this.client.call('get', apiPath + '/' + {{ parameter.name }}, apiHeaders);
chunksUploaded = response.chunksUploaded;
} catch(e) {
}
@@ -40,20 +40,24 @@
}
const start = currentChunkStart;
- const end = Math.min(((start + client.CHUNK_SIZE) - 1), size);
+ const end = currentChunkStart + currentChunkSize - 1;
if(!lastUpload || currentChunkStart !== 0) {
- headers['content-range'] = 'bytes ' + start + '-' + end + '/' + size;
+ apiHeaders['content-range'] = 'bytes ' + start + '-' + end + '/' + size;
}
if (id) {
- headers['x-{{spec.title | caseLower }}-id'] = id;
+ apiHeaders['x-{{spec.title | caseLower }}-id'] = id;
}
- const stream = Stream.Readable.from(currentChunk);
- payload['{{ parameter.name }}'] = { type: 'file', file: stream, filename: {{ parameter.name }}.filename };
+ payload['{{ parameter.name }}'] = {
+ type: 'file',
+ file: currentChunk,
+ filename: {{ parameter.name }}.filename,
+ size: currentChunkSize
+ };
- response = await selfClient.call('{{ method.method | caseLower }}', path, headers, payload{% if method.type == 'location' %}, 'arraybuffer'{% endif %});
+ response = await selfClient.call('{{ method.method | caseLower }}', apiPath, apiHeaders, payload{% if method.type == 'location' %}, 'arraybuffer'{% endif %});
if (!id) {
id = response['$id'];
@@ -92,6 +96,7 @@
if(chunkSize + currentChunkSize == client.CHUNK_SIZE) {
// Upload chunk
currentChunk = Buffer.concat([currentChunk, chunk]);
+ currentChunkSize = Buffer.byteLength(currentChunk);
await uploadChunk();
currentChunk = Buffer.from('');
currentChunkSize = 0;
diff --git a/templates/node/index.d.ts.twig b/templates/node/index.d.ts.twig
index adae9cf5d..721eae574 100644
--- a/templates/node/index.d.ts.twig
+++ b/templates/node/index.d.ts.twig
@@ -208,6 +208,7 @@ declare module "{{ language.params.npmPackage|caseDash }}" {
static guests(): string;
static team(id: string, role?: string): string;
static member(id: string): string;
+ static label(name: string): string;
}
{% for service in spec.services %}
diff --git a/templates/node/lib/client.js.twig b/templates/node/lib/client.js.twig
index b2538e8e8..c3b8130e5 100644
--- a/templates/node/lib/client.js.twig
+++ b/templates/node/lib/client.js.twig
@@ -1,5 +1,6 @@
const os = require('os');
const URL = require('url').URL;
+const https = require("https");
const axios = require('axios');
const FormData = require('form-data');
const {{spec.title | caseUcfirst}}Exception = require('./exception.js');
@@ -81,11 +82,6 @@ class Client {
}
async call(method, path = '', headers = {}, params = {}, responseType = 'json') {
- if(this.selfSigned) { // Allow self signed requests
- process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = 0;
- }
-
-
headers = Object.assign({}, this.headers, headers);
let contentType = headers['content-type'].toLowerCase();
@@ -125,6 +121,10 @@ class Client {
json: (contentType.startsWith('application/json')),
responseType: responseType
};
+ if (this.selfSigned) {
+ // Allow self signed requests
+ options.httpsAgent = new https.Agent({ rejectUnauthorized: false });
+ }
try {
let response = await axios(options);
return response.data;
diff --git a/templates/node/lib/query.js.twig b/templates/node/lib/query.js.twig
index 06e0b389d..675b02b49 100644
--- a/templates/node/lib/query.js.twig
+++ b/templates/node/lib/query.js.twig
@@ -24,7 +24,7 @@ class Query {
`isNotNull("${attribute}")`;
static between = (attribute, start, end) =>
- Query.addQuery(attribute, "between", [start, end]);
+ `between("${attribute}", ${Query.parseValues(start)}, ${Query.parseValues(end)})`
static startsWith = (attribute, value) =>
Query.addQuery(attribute, "startsWith", value);
diff --git a/templates/node/lib/role.js.twig b/templates/node/lib/role.js.twig
index 5c168c239..41b3cb5c4 100644
--- a/templates/node/lib/role.js.twig
+++ b/templates/node/lib/role.js.twig
@@ -1,31 +1,102 @@
+/**
+ * Helper class to generate role strings for `Permission`.
+ */
class Role {
+
+ /**
+ * Grants access to anyone.
+ *
+ * This includes authenticated and unauthenticated users.
+ *
+ * @returns {string}
+ */
static any = () => {
return 'any'
}
+
+ /**
+ * Grants access to a specific user by user ID.
+ *
+ * You can optionally pass verified or unverified for
+ * `status` to target specific types of users.
+ *
+ * @param {string} id
+ * @param {string} status
+ * @returns {string}
+ */
static user = (id, status = '') => {
- if(status === '') {
+ if (status === '') {
return `user:${id}`
}
return `user:${id}/${status}`
}
+
+ /**
+ * Grants access to any authenticated or anonymous user.
+ *
+ * You can optionally pass verified or unverified for
+ * `status` to target specific types of users.
+ *
+ * @param {string} status
+ * @returns {string}
+ */
static users = (status = '') => {
- if(status === '') {
+ if (status === '') {
return 'users'
}
return `users/${status}`
}
+
+ /**
+ * Grants access to any guest user without a session.
+ *
+ * Authenticated users don't have access to this role.
+ *
+ * @returns {string}
+ */
static guests = () => {
return 'guests'
}
+
+ /**
+ * Grants access to a team by team ID.
+ *
+ * You can optionally pass a role for `role` to target
+ * team members with the specified role.
+ *
+ * @param {string} id
+ * @param {string} role
+ * @returns {string}
+ */
static team = (id, role = '') => {
- if(role === '') {
+ if (role === '') {
return 'team:' + id
}
return 'team:' + id + '/' + role
}
+
+ /**
+ * Grants access to a specific member of a team.
+ *
+ * When the member is removed from the team, they will
+ * no longer have access.
+ *
+ * @param {string} id
+ * @returns {string}
+ */
static member = (id) => {
return 'member:' + id
}
+
+ /**
+ * Grants access to a user with the specified label.
+ *
+ * @param {string} name
+ * @returns {string}
+ */
+ static label = (name) => {
+ return 'label:' + name;
+ }
}
module.exports = Role;
\ No newline at end of file
diff --git a/templates/node/lib/services/service.js.twig b/templates/node/lib/services/service.js.twig
index 5cc919ab3..acfa340a3 100644
--- a/templates/node/lib/services/service.js.twig
+++ b/templates/node/lib/services/service.js.twig
@@ -29,7 +29,7 @@ class {{ service.name | caseUcfirst }} extends Service {
* @returns {Promise}
*/
async {{ method.name | caseCamel }}({% for parameter in method.parameters.all %}{{ parameter.name | caseCamel | escapeKeyword }}{% if not loop.last %}, {% endif %}{% endfor %}{% if 'multipart/form-data' in method.consumes %}, onProgress = () => {}{% endif %}) {
- let path = '{{ method.path }}'{% for parameter in method.parameters.path %}.replace('{{ '{' }}{{ parameter.name | caseCamel }}{{ '}' }}', {{ parameter.name | caseCamel | escapeKeyword }}){% endfor %};
+ const apiPath = '{{ method.path }}'{% for parameter in method.parameters.path %}.replace('{{ '{' }}{{ parameter.name | caseCamel }}{{ '}' }}', {{ parameter.name | caseCamel | escapeKeyword }}){% endfor %};
{{ include ('node/base/params.twig')}}
{% if 'multipart/form-data' in method.consumes %}
{{ include ('node/base/requests/file.twig')}}
diff --git a/templates/node/package.json.twig b/templates/node/package.json.twig
index fa7719023..6a8eae40c 100644
--- a/templates/node/package.json.twig
+++ b/templates/node/package.json.twig
@@ -14,7 +14,7 @@
"@types/node": "^18.16.1"
},
"dependencies": {
- "axios": "^1.3.6",
+ "axios": "^1.4.0",
"form-data": "^4.0.0"
}
}
diff --git a/templates/php/base/params.twig b/templates/php/base/params.twig
index 3e0b08733..8557222a4 100644
--- a/templates/php/base/params.twig
+++ b/templates/php/base/params.twig
@@ -1,4 +1,4 @@
- $params = [];
+ $apiParams = [];
{% if method.parameters.all | length %}
{% for parameter in method.parameters.all %}
{% if parameter.required and not parameter.nullable %}
@@ -9,19 +9,19 @@
{% endfor %}
{% for parameter in method.parameters.query %}
if (!is_null(${{ parameter.name | caseCamel | escapeKeyword }})) {
- $params['{{ parameter.name }}'] = ${{ parameter.name | caseCamel | escapeKeyword }};
+ $apiParams['{{ parameter.name }}'] = ${{ parameter.name | caseCamel | escapeKeyword }};
}
{% endfor %}
{% for parameter in method.parameters.body %}
if (!is_null(${{ parameter.name | caseCamel | escapeKeyword }})) {
- $params['{{ parameter.name }}'] = ${{ parameter.name | caseCamel | escapeKeyword }};
+ $apiParams['{{ parameter.name }}'] = ${{ parameter.name | caseCamel | escapeKeyword }};
}
{% endfor %}
{% for parameter in method.parameters.formData %}
if (!is_null(${{ parameter.name | caseCamel | escapeKeyword }})) {
- $params['{{ parameter.name }}'] = ${{ parameter.name | caseCamel | escapeKeyword }};
+ $apiParams['{{ parameter.name }}'] = ${{ parameter.name | caseCamel | escapeKeyword }};
}
{% endfor %}
{% endif %}
\ No newline at end of file
diff --git a/templates/php/base/requests/api.twig b/templates/php/base/requests/api.twig
index 72aee3648..b22cf8ca8 100644
--- a/templates/php/base/requests/api.twig
+++ b/templates/php/base/requests/api.twig
@@ -1,8 +1,8 @@
- return $this->client->call(Client::METHOD_{{ method.method | caseUpper }}, $path, [
+ return $this->client->call(Client::METHOD_{{ method.method | caseUpper }}, $apiPath, [
{% for parameter in method.parameters.header %}
'{{ parameter.name }}' => ${{ parameter.name | caseCamel | escapeKeyword }},
{% endfor %}
{% for key, header in method.headers %}
'{{ key }}' => '{{ header }}',
{% endfor %}
- ], $params);
\ No newline at end of file
+ ], $apiParams);
\ No newline at end of file
diff --git a/templates/php/base/requests/file.twig b/templates/php/base/requests/file.twig
index 7f9b2d5dc..e80c793da 100644
--- a/templates/php/base/requests/file.twig
+++ b/templates/php/base/requests/file.twig
@@ -8,15 +8,15 @@
$mimeType = ${{ parameter.name | caseCamel }}->getMimeType();
$postedName = ${{ parameter.name | caseCamel }}->getFilename();
if ($size <= Client::CHUNK_SIZE) {
- $params['{{ parameter.name | caseCamel }}'] = new \CURLFile('data://' . $mimeType . ';base64,' . base64_encode(${{ parameter.name | caseCamel }}->getData()), $mimeType, $postedName);
- return $this->client->call(Client::METHOD_POST, $path, [
+ $apiParams['{{ parameter.name | caseCamel }}'] = new \CURLFile('data://' . $mimeType . ';base64,' . base64_encode(${{ parameter.name | caseCamel }}->getData()), $mimeType, $postedName);
+ return $this->client->call(Client::METHOD_POST, $apiPath, [
{% for param in method.parameters.header %}
'{{ param.name }}' => ${{ param.name | caseCamel }},
{% endfor %}
{% for key, header in method.headers %}
'{{ key }}' => '{{ header }}',
{% endfor %}
- ], $params);
+ ], $apiParams);
}
} else {
$size = filesize(${{ parameter.name | caseCamel }}->getPath());
@@ -24,15 +24,15 @@
$postedName = ${{ parameter.name | caseCamel }}->getFilename() ?? basename(${{ parameter.name | caseCamel }}->getPath());
//send single file if size is less than or equal to 5MB
if ($size <= Client::CHUNK_SIZE) {
- $params['{{ parameter.name }}'] = new \CURLFile(${{ parameter.name | caseCamel }}->getPath(), $mimeType, $postedName);
- return $this->client->call(Client::METHOD_{{ method.method | caseUpper }}, $path, [
+ $apiParams['{{ parameter.name }}'] = new \CURLFile(${{ parameter.name | caseCamel }}->getPath(), $mimeType, $postedName);
+ return $this->client->call(Client::METHOD_{{ method.method | caseUpper }}, $apiPath, [
{% for param in method.parameters.header %}
'{{ param.name }}' => ${{ param.name | caseCamel }},
{% endfor %}
{% for key, header in method.headers %}
'{{ key }}' => '{{ header }}',
{% endfor %}
- ], $params);
+ ], $apiParams);
}
}
@@ -43,7 +43,7 @@
{% if parameter.isUploadID %}
if(${{ parameter.name | caseCamel | escapeKeyword }} != 'unique()') {
try {
- $response = $this->client->call(Client::METHOD_GET, $path . '/' . ${{ parameter.name }});
+ $response = $this->client->call(Client::METHOD_GET, $apiPath . '/' . ${{ parameter.name }});
$counter = $response['chunksUploaded'] ?? 0;
} catch(\Exception $e) {
}
@@ -51,7 +51,7 @@
{% endif %}
{% endfor %}
- $headers = ['content-type' => 'multipart/form-data'];
+ $apiHeaders = ['content-type' => 'multipart/form-data'];
$handle = null;
if(!empty(${{parameter.name}}->getPath())) {
@@ -67,12 +67,12 @@
} else {
$chunk = substr($file->getData(), $start, Client::CHUNK_SIZE);
}
- $params['{{ parameter.name }}'] = new \CURLFile('data://' . $mimeType . ';base64,' . base64_encode($chunk), $mimeType, $postedName);
- $headers['content-range'] = 'bytes ' . ($counter * Client::CHUNK_SIZE) . '-' . min(((($counter * Client::CHUNK_SIZE) + Client::CHUNK_SIZE) - 1), $size) . '/' . $size;
+ $apiParams['{{ parameter.name }}'] = new \CURLFile('data://' . $mimeType . ';base64,' . base64_encode($chunk), $mimeType, $postedName);
+ $apiHeaders['content-range'] = 'bytes ' . ($counter * Client::CHUNK_SIZE) . '-' . min(((($counter * Client::CHUNK_SIZE) + Client::CHUNK_SIZE) - 1), $size - 1) . '/' . $size;
if(!empty($id)) {
- $headers['x-{{spec.title | caseLower }}-id'] = $id;
+ $apiHeaders['x-{{spec.title | caseLower }}-id'] = $id;
}
- $response = $this->client->call(Client::METHOD_POST, $path, $headers, $params);
+ $response = $this->client->call(Client::METHOD_POST, $apiPath, $apiHeaders, $apiParams);
$counter++;
$start += Client::CHUNK_SIZE;
if(empty($id)) {
@@ -81,7 +81,7 @@
if($onProgress !== null) {
$onProgress([
'$id' => $response['$id'],
- 'progress' => min(((($counter * Client::CHUNK_SIZE) + Client::CHUNK_SIZE) - 1), $size) / $size * 100,
+ 'progress' => min(((($counter * Client::CHUNK_SIZE) + Client::CHUNK_SIZE)), $size) / $size * 100,
'sizeUploaded' => min($counter * Client::CHUNK_SIZE),
'chunksTotal' => $response['chunksTotal'],
'chunksUploaded' => $response['chunksUploaded'],
diff --git a/templates/php/composer.json.twig b/templates/php/composer.json.twig
index bf7288abe..d02b22973 100644
--- a/templates/php/composer.json.twig
+++ b/templates/php/composer.json.twig
@@ -18,7 +18,8 @@
"ext-json": "*"
},
"require-dev": {
- "phpunit/phpunit": "3.7.35"
+ "phpunit/phpunit": "^10",
+ "mockery/mockery": "^1.6.6"
},
"minimum-stability": "dev"
}
\ No newline at end of file
diff --git a/templates/php/src/Query.php.twig b/templates/php/src/Query.php.twig
index 5f4d515fd..e249e8fcc 100644
--- a/templates/php/src/Query.php.twig
+++ b/templates/php/src/Query.php.twig
@@ -120,7 +120,10 @@ class Query
*/
public static function between(string $attribute, $start, $end): string
{
- return self::addQuery($attribute, 'between', [$start, $end]);
+ $start = self::parseValues($start);
+ $end = self::parseValues($end);
+
+ return "between(\"{$attribute}\", {$start}, {$end})";
}
/**
diff --git a/templates/php/src/Role.php.twig b/templates/php/src/Role.php.twig
index 87cf43a1d..a412ce97e 100644
--- a/templates/php/src/Role.php.twig
+++ b/templates/php/src/Role.php.twig
@@ -2,12 +2,33 @@
namespace {{ spec.title | caseUcfirst }};
+/**
+ * Helper class to generate role strings for Permission.
+ */
class Role
{
+ /**
+ * Grants access to anyone.
+ *
+ * This includes authenticated and unauthenticated users.
+ *
+ * @return string
+ */
public static function any(): string
{
return 'any';
}
+
+ /**
+ * Grants access to a specific user by user ID.
+ *
+ * You can optionally pass verified or unverified for
+ * `status` to target specific types of users.
+ *
+ * @param string $id
+ * @param string $status
+ * @return string
+ */
public static function user(string $id, string $status = ""): string
{
if(empty($status)) {
@@ -15,6 +36,16 @@ class Role
}
return "user:$id/$status";
}
+
+ /**
+ * Grants access to any authenticated or anonymous user.
+ *
+ * You can optionally pass verified or unverified for
+ * `status` to target specific types of users.
+ *
+ * @param string $status
+ * @return string
+ */
public static function users(string $status = ""): string
{
if(empty($status)) {
@@ -22,10 +53,29 @@ class Role
}
return "users/$status";
}
+
+ /**
+ * Grants access to any guest user without a session.
+ *
+ * Authenticated users don't have access to this role.
+ *
+ * @return string
+ */
public static function guests(): string
{
return 'guests';
}
+
+ /**
+ * Grants access to a team by team ID.
+ *
+ * You can optionally pass a role for `role` to target
+ * team members with the specified role.
+ *
+ * @param string $id
+ * @param string $role
+ * @return string
+ */
public static function team(string $id, string $role = ""): string
{
if(empty($role)) {
@@ -33,8 +83,29 @@ class Role
}
return "team:$id/$role";
}
+
+ /**
+ * Grants access to a specific member of a team.
+ *
+ * When the member is removed from the team, they will
+ * no longer have access.
+ *
+ * @param string $id
+ * @return string
+ */
public static function member(string $id): string
{
return "member:$id";
}
+
+ /**
+ * Grants access to a user with the specified label.
+ *
+ * @param string $name
+ * @return string
+ */
+ public static function label(string $name): string
+ {
+ return "label:$name";
+ }
}
\ No newline at end of file
diff --git a/templates/php/src/Services/Service.php.twig b/templates/php/src/Services/Service.php.twig
index 3e110cffe..73b9c42c8 100644
--- a/templates/php/src/Services/Service.php.twig
+++ b/templates/php/src/Services/Service.php.twig
@@ -31,7 +31,7 @@ class {{ service.name | caseUcfirst }} extends Service
*/
public function {{ method.name | caseCamel }}({% for parameter in method.parameters.all %}{{ parameter | typeName }}${{ parameter.name | caseCamel | escapeKeyword }}{% if not parameter.required %} = null{% endif %}{% if not loop.last %}, {% endif %}{% endfor %}{% if 'multipart/form-data' in method.consumes %}, callable $onProgress = null{% endif %}): {{ method | getReturn }}
{
- $path = str_replace([{% for parameter in method.parameters.path %}'{{ '{' }}{{ parameter.name | caseCamel }}{{ '}' }}'{% if not loop.last %}, {% endif %}{% endfor %}], [{% for parameter in method.parameters.path %}${{ parameter.name | caseCamel | escapeKeyword }}{% if not loop.last %}, {% endif %}{% endfor %}], '{{ method.path }}');
+ $apiPath = str_replace([{% for parameter in method.parameters.path %}'{{ '{' }}{{ parameter.name | caseCamel }}{{ '}' }}'{% if not loop.last %}, {% endif %}{% endfor %}], [{% for parameter in method.parameters.path %}${{ parameter.name | caseCamel | escapeKeyword }}{% if not loop.last %}, {% endif %}{% endfor %}], '{{ method.path }}');
{{ include('php/base/params.twig') }}
{% if 'multipart/form-data' in method.consumes %}
diff --git a/templates/php/tests/IDTest.php.twig b/templates/php/tests/IDTest.php.twig
new file mode 100644
index 000000000..a48a6b5d8
--- /dev/null
+++ b/templates/php/tests/IDTest.php.twig
@@ -0,0 +1,15 @@
+assertSame('unique()', ID::unique());
+ }
+
+ public function testCustom(): void {
+ $this->assertSame('custom', ID::custom('custom'));
+ }
+}
diff --git a/templates/php/tests/PermissionTest.php.twig b/templates/php/tests/PermissionTest.php.twig
new file mode 100644
index 000000000..cf4078407
--- /dev/null
+++ b/templates/php/tests/PermissionTest.php.twig
@@ -0,0 +1,27 @@
+assertSame('read("any")', Permission::read(Role::any()));
+ }
+
+ public function testWrite(): void {
+ $this->assertSame('write("any")', Permission::write(Role::any()));
+ }
+
+ public function testCreate(): void {
+ $this->assertSame('create("any")', Permission::create(Role::any()));
+ }
+
+ public function testUpdate(): void {
+ $this->assertSame('update("any")', Permission::update(Role::any()));
+ }
+
+ public function testDelete(): void {
+ $this->assertSame('delete("any")', Permission::delete(Role::any()));
+ }
+}
diff --git a/templates/php/tests/QueryTest.php.twig b/templates/php/tests/QueryTest.php.twig
new file mode 100644
index 000000000..900c7e429
--- /dev/null
+++ b/templates/php/tests/QueryTest.php.twig
@@ -0,0 +1,149 @@
+description = $description;
+ $this->value = $value;
+ $this->expectedValues = $expectedValues;
+ }
+}
+
+final class QueryTest extends TestCase {
+ /**
+ * @var BasicFilterQueryTest[] $tests
+ */
+ private $tests;
+
+ function __construct(string $name)
+ {
+ parent::__construct($name);
+ $this->tests = array(
+ new BasicFilterQueryTest('with a string', 's', '["s"]'),
+ new BasicFilterQueryTest('with a integer', 1, '[1]'),
+ new BasicFilterQueryTest('with a double', 1.2, '[1.2]'),
+ new BasicFilterQueryTest('with a whole number double', 1.0, '[1]'),
+ new BasicFilterQueryTest('with a bool', false, '[false]'),
+ new BasicFilterQueryTest('with a list', ['a', 'b', 'c'], '["a","b","c"]'),
+ );
+ }
+
+ public function testBasicFilterEqual(): void {
+ foreach($this->tests as $test) {
+ $this->assertSame(
+ "equal(\"attr\", $test->expectedValues)",
+ Query::equal('attr', $test->value),
+ $test->description,
+ );
+ }
+ }
+
+ public function testBasicFilterNotEqual(): void {
+ foreach($this->tests as $test) {
+ $this->assertSame(
+ "notEqual(\"attr\", $test->expectedValues)",
+ Query::notEqual('attr', $test->value),
+ $test->description,
+ );
+ }
+ }
+
+ public function testBasicFilterLessThan(): void {
+ foreach($this->tests as $test) {
+ $this->assertSame(
+ "lessThan(\"attr\", $test->expectedValues)",
+ Query::lessThan('attr', $test->value),
+ $test->description,
+ );
+ }
+ }
+
+ public function testBasicFilterLessThanEqual(): void {
+ foreach($this->tests as $test) {
+ $this->assertSame(
+ "lessThanEqual(\"attr\", $test->expectedValues)",
+ Query::lessThanEqual('attr', $test->value),
+ $test->description,
+ );
+ }
+ }
+
+ public function testBasicFilterGreaterThan(): void {
+ foreach($this->tests as $test) {
+ $this->assertSame(
+ "greaterThan(\"attr\", $test->expectedValues)",
+ Query::greaterThan('attr', $test->value),
+ $test->description,
+ );
+ }
+ }
+
+ public function testBasicFilterGreaterThanEqual(): void {
+ foreach($this->tests as $test) {
+ $this->assertSame(
+ "greaterThanEqual(\"attr\", $test->expectedValues)",
+ Query::greaterThanEqual('attr', $test->value),
+ $test->description,
+ );
+ }
+ }
+
+ public function testSearch(): void {
+ $this->assertSame('search("attr", ["keyword1 keyword2"])', Query::search('attr', 'keyword1 keyword2'));
+ }
+
+ public function testIsNull(): void {
+ $this->assertSame('isNull("attr")', Query::isNull('attr'));
+ }
+
+ public function testIsNotNull(): void {
+ $this->assertSame('isNotNull("attr")', Query::isNotNull('attr'));
+ }
+
+ public function testBetweenWithIntegers(): void {
+ $this->assertSame('between("attr", 1, 2)', Query::between('attr', 1, 2));
+ }
+
+ public function testBetweenWithDoubles(): void {
+ $this->assertSame('between("attr", 1, 2)', Query::between('attr', 1.0, 2.0));
+ }
+
+ public function testBetweenWithStrings(): void {
+ $this->assertSame('between("attr", "a", "z")', Query::between('attr', 'a', 'z'));
+ }
+
+ public function testSelect(): void {
+ $this->assertSame('select(["attr1","attr2"])', Query::select(['attr1', 'attr2']));
+ }
+
+ public function testOrderAsc(): void {
+ $this->assertSame('orderAsc("attr")', Query::orderAsc('attr'));
+ }
+
+ public function testOrderDesc(): void {
+ $this->assertSame('orderDesc("attr")', Query::orderDesc('attr'));
+ }
+
+ public function testCursorBefore(): void {
+ $this->assertSame('cursorBefore("attr")', Query::cursorBefore('attr'));
+ }
+
+ public function testCursorAfter(): void {
+ $this->assertSame('cursorAfter("attr")', Query::cursorAfter('attr'));
+ }
+
+ public function testLimit(): void {
+ $this->assertSame('limit(1)', Query::limit(1));
+ }
+
+ public function testOffset(): void {
+ $this->assertSame('offset(1)', Query::offset(1));
+ }
+}
diff --git a/templates/php/tests/RoleTest.php.twig b/templates/php/tests/RoleTest.php.twig
new file mode 100644
index 000000000..9a800d54d
--- /dev/null
+++ b/templates/php/tests/RoleTest.php.twig
@@ -0,0 +1,47 @@
+assertSame('any', Role::any());
+ }
+
+ public function testUserWithoutStatus(): void {
+ $this->assertSame('user:custom', Role::user('custom'));
+ }
+
+ public function testUserWithStatus(): void {
+ $this->assertSame('user:custom/verified', Role::user('custom', 'verified'));
+ }
+
+ public function testUsersWithoutStatus(): void {
+ $this->assertSame('users', Role::users());
+ }
+
+ public function testUsersWithStatus(): void {
+ $this->assertSame('users/verified', Role::users('verified'));
+ }
+
+ public function testGuests(): void {
+ $this->assertSame('guests', Role::guests());
+ }
+
+ public function testTeamWithoutRole(): void {
+ $this->assertSame('team:custom', Role::team('custom'));
+ }
+
+ public function testTeamWithRole(): void {
+ $this->assertSame('team:custom/owner', Role::team('custom', 'owner'));
+ }
+
+ public function testMember(): void {
+ $this->assertSame('member:custom', Role::member('custom'));
+ }
+
+ public function testLabel(): void {
+ $this->assertSame('label:admin', Role::label('admin'));
+ }
+}
diff --git a/templates/php/tests/Services/ServiceTest.php.twig b/templates/php/tests/Services/ServiceTest.php.twig
new file mode 100644
index 000000000..6e31e9bf3
--- /dev/null
+++ b/templates/php/tests/Services/ServiceTest.php.twig
@@ -0,0 +1,44 @@
+client = Mockery::mock(Client::class);
+ $this->{{ service.name | caseCamel }} = new {{ service.name | caseUcfirst }}($this->client);
+ }
+
+{% for method in service.methods %}
+ public function testMethod{{method.name | caseUcfirst}}(): void {
+ {%~ if method.responseModel and method.responseModel != 'any' ~%}
+ $data = array(
+ {%- for definition in spec.definitions ~%}{%~ if definition.name == method.responseModel -%}{%~ for property in definition.properties | filter((param) => param.required) ~%}
+ "{{property.name | escapeDollarSign}}" => {% if property.type == 'object' %}array(){% elseif property.type == 'array' %}array(){% elseif property.type == 'string' %}"{{property.example | escapeDollarSign}}"{% elseif property.type == 'boolean' %}true{% else %}{{property.example}}{% endif %},{%~ endfor ~%}{% set break = true %}{%- else -%}{% set continue = true %}{%- endif -%}{%~ endfor -%}
+ );
+ {%~ elseif (method.responseModel and method.responseModel == 'any') or method.type == 'webAuth' ~%}
+ $data = array();
+ {%~ else ~%}
+ $data = '';
+ {%~ endif ~%}
+
+ $this->client
+ ->allows()->call(Mockery::any(), Mockery::any(), Mockery::any(), Mockery::any())
+ ->andReturn($data);
+
+ $response = $this->{{service.name | caseCamel}}->{{method.name | caseCamel}}({%~ for parameter in method.parameters.all | filter((param) => param.required) ~%}
+ {% if parameter.type == 'object' %}array(){% elseif parameter.type == 'array' %}array(){% elseif parameter.type == 'file' %}InputFile::withData('', "image/png"){% elseif parameter.type == 'boolean' %}true{% elseif parameter.type == 'string' %}"{% if parameter.example is not empty %}{{parameter.example | escapeDollarSign}}{% endif %}"{% elseif parameter.type == 'integer' and parameter['x-example'] is empty %}1{% elseif parameter.type == 'number' and parameter['x-example'] is empty %}1.0{% else %}{{parameter.example}}{%~ endif ~%}{% if not loop.last %},{% endif %}{%~ endfor ~%}
+ );
+
+ $this->assertSame($data, $response);
+ }
+
+{% endfor %}
+}
diff --git a/templates/python/.travis.yml.twig b/templates/python/.travis.yml.twig
index 712f791bb..bf9a9dffd 100644
--- a/templates/python/.travis.yml.twig
+++ b/templates/python/.travis.yml.twig
@@ -1,5 +1,7 @@
language: python
+dist: bionic
+
python:
- "3.8"
diff --git a/templates/python/base/params.twig b/templates/python/base/params.twig
index faf595f08..8a574ec96 100644
--- a/templates/python/base/params.twig
+++ b/templates/python/base/params.twig
@@ -1,31 +1,31 @@
- params = {}
+ api_params = {}
{% if method.parameters.all | length %}
{% for parameter in method.parameters.all %}
-{% if parameter.required %}
+{% if parameter.required and not parameter.nullable %}
if {{ parameter.name | escapeKeyword | caseSnake }} is None:
raise {{spec.title | caseUcfirst}}Exception('Missing required parameter: "{{ parameter.name | escapeKeyword | caseSnake }}"')
{% endif %}
{% endfor %}
{% for parameter in method.parameters.path %}
- path = path.replace('{{ '{' }}{{ parameter.name | caseCamel }}{{ '}' }}', {{ parameter.name | escapeKeyword | caseSnake }})
+ api_path = api_path.replace('{{ '{' }}{{ parameter.name | caseCamel }}{{ '}' }}', {{ parameter.name | escapeKeyword | caseSnake }})
{% endfor %}
{% for parameter in method.parameters.query %}
- params['{{ parameter.name }}'] = {{ parameter.name | escapeKeyword | caseSnake }}
+ api_params['{{ parameter.name }}'] = {{ parameter.name | escapeKeyword | caseSnake }}
{% endfor %}
{% for parameter in method.parameters.body %}
{% if method.consumes[0] == "multipart/form-data" and ( parameter.type != "string" and parameter.type != "array" ) %}
- params['{{ parameter.name }}'] = str({{ parameter.name | escapeKeyword | caseSnake }}).lower() if type({{ parameter.name | escapeKeyword | caseSnake }}) is bool else {{ parameter.name | escapeKeyword | caseSnake }}
+ api_params['{{ parameter.name }}'] = str({{ parameter.name | escapeKeyword | caseSnake }}).lower() if type({{ parameter.name | escapeKeyword | caseSnake }}) is bool else {{ parameter.name | escapeKeyword | caseSnake }}
{% else %}
- params['{{ parameter.name }}'] = {{ parameter.name | escapeKeyword | caseSnake }}
+ api_params['{{ parameter.name }}'] = {{ parameter.name | escapeKeyword | caseSnake }}
{% endif %}
{% endfor %}
{% for parameter in method.parameters.formData %}
{% if method.consumes[0] == "multipart/form-data" and ( parameter.type != "string" and parameter.type != "array" ) %}
- params['{{ parameter.name }}'] = str({{ parameter.name | escapeKeyword | caseSnake }}).lower() if type({{ parameter.name | escapeKeyword | caseSnake }}) is bool else {{ parameter.name | escapeKeyword | caseSnake }}
+ api_params['{{ parameter.name }}'] = str({{ parameter.name | escapeKeyword | caseSnake }}).lower() if type({{ parameter.name | escapeKeyword | caseSnake }}) is bool else {{ parameter.name | escapeKeyword | caseSnake }}
{% else %}
- params['{{ parameter.name }}'] = {{ parameter.name | escapeKeyword | caseSnake }}
+ api_params['{{ parameter.name }}'] = {{ parameter.name | escapeKeyword | caseSnake }}
{% endif %}
{% endfor %}
{% endif %}
\ No newline at end of file
diff --git a/templates/python/base/requests/api.twig b/templates/python/base/requests/api.twig
index defae2819..e305a8d9b 100644
--- a/templates/python/base/requests/api.twig
+++ b/templates/python/base/requests/api.twig
@@ -1,8 +1,8 @@
- return self.client.call('{{ method.method | caseLower }}', path, {
+ return self.client.call('{{ method.method | caseLower }}', api_path, {
{% for parameter in method.parameters.header %}
'{{ parameter.name }}': {{ parameter.name | escapeKeyword | caseSnake }},
{% endfor %}
{% for key, header in method.headers %}
'{{ key }}': '{{ header }}',
{% endfor %}
- }, params)
\ No newline at end of file
+ }, api_params)
\ No newline at end of file
diff --git a/templates/python/base/requests/file.twig b/templates/python/base/requests/file.twig
index 8a8d223ff..52b3cc691 100644
--- a/templates/python/base/requests/file.twig
+++ b/templates/python/base/requests/file.twig
@@ -12,11 +12,11 @@
{% endif %}
{% endfor %}
- return self.client.chunked_upload(path, {
+ return self.client.chunked_upload(api_path, {
{% for parameter in method.parameters.header %}
'{{ parameter.name }}': {{ parameter.name | escapeKeyword | caseSnake }},
{% endfor %}
{% for key, header in method.headers %}
'{{ key }}': '{{ header }}',
{% endfor %}
- }, params, param_name, on_progress, upload_id)
\ No newline at end of file
+ }, api_params, param_name, on_progress, upload_id)
\ No newline at end of file
diff --git a/templates/python/package/client.py.twig b/templates/python/package/client.py.twig
index 057693f1f..8fb6947e1 100644
--- a/templates/python/package/client.py.twig
+++ b/templates/python/package/client.py.twig
@@ -50,6 +50,8 @@ class Client:
if params is None:
params = {}
+ params = {k: v for k, v in params.items() if v is not None} # Remove None values from params dictionary
+
data = {}
json = {}
files = {}
@@ -158,7 +160,7 @@ class Client:
input_file.data = input[offset:end]
params[param_name] = input_file
- headers["content-range"] = f'bytes {offset}-{min((offset + self._chunk_size) - 1, size)}/{size}'
+ headers["content-range"] = f'bytes {offset}-{min((offset + self._chunk_size) - 1, size - 1)}/{size}'
result = self.call(
'post',
@@ -173,7 +175,7 @@ class Client:
headers["x-{{ spec.title | caseLower }}-id"] = result["$id"]
if on_progress is not None:
- end = min((((counter * self._chunk_size) + self._chunk_size) - 1), size)
+ end = min((((counter * self._chunk_size) + self._chunk_size) - 1), size - 1)
on_progress({
"$id": result["$id"],
"progress": min(offset, size)/size * 100,
diff --git a/templates/python/package/query.py.twig b/templates/python/package/query.py.twig
index d92930477..65e8dc42e 100644
--- a/templates/python/package/query.py.twig
+++ b/templates/python/package/query.py.twig
@@ -33,7 +33,7 @@ class Query:
@staticmethod
def between(attribute, start, end) -> str:
- return Query.add_query(attribute, "between", [start, end])
+ return f'between("{attribute}", {Query.parseValues(start)}, {Query.parseValues(end)})'
@staticmethod
def starts_with(attribute, value) -> str:
diff --git a/templates/python/package/role.py.twig b/templates/python/package/role.py.twig
index ae3b7db50..3abf09c7a 100644
--- a/templates/python/package/role.py.twig
+++ b/templates/python/package/role.py.twig
@@ -1,30 +1,111 @@
class Role:
+ """Helper class to generate role strings for `Permission`."""
@staticmethod
def any() -> str:
+ """Grants access to anyone.
+
+ This includes authenticated and unauthenticated users.
+ """
return 'any'
@staticmethod
def user(id, status: str = "") -> str:
+ """Grants access to a specific user by user ID.
+
+ You can optionally pass verified or unverified for
+ `status` to target specific types of users.
+
+ Parameters
+ ----------
+ id : str
+ status : str, optional
+
+ Returns
+ -------
+ str
+ """
if status:
return f'user:{id}/{status}'
return f'user:{id}'
@staticmethod
def users(status: str = "") -> str:
+ """Grants access to any authenticated or anonymous user.
+
+ You can optionally pass verified or unverified for
+ `status` to target specific types of users.
+
+ Parameters
+ ----------
+ status : str, optional
+
+ Returns
+ -------
+ str
+ """
if status:
return f'users/{status}'
return 'users'
@staticmethod
def guests() -> str:
+ """Grants access to any guest user without a session.
+
+ Authenticated users don't have access to this role.
+
+ Returns
+ -------
+ str
+ """
return 'guests'
@staticmethod
def team(id, role: str = "") -> str:
+ """Grants access to a team by team ID.
+
+ You can optionally pass a role for `role` to target
+ team members with the specified role.
+
+ Parameters
+ ----------
+ id : str
+ role : str, optional
+
+ Returns
+ -------
+ str
+ """
if role:
return f'team:{id}/{role}'
return f'team:{id}'
@staticmethod
def member(id) -> str:
+ """Grants access to a specific member of a team.
+
+ When the member is removed from the team, they will
+ no longer have access.
+
+ Parameters
+ ----------
+ id : str
+
+ Returns
+ -------
+ str
+ """
return f'member:{id}'
+
+ @staticmethod
+ def label(name):
+ """Grants access to a user with the specified label.
+
+ Parameters
+ ----------
+ name : str
+
+ Returns
+ -------
+ str
+ """
+ return f'label:{name}'
diff --git a/templates/python/package/services/service.py.twig b/templates/python/package/services/service.py.twig
index df665f003..4f617e437 100644
--- a/templates/python/package/services/service.py.twig
+++ b/templates/python/package/services/service.py.twig
@@ -12,7 +12,7 @@ class {{ service.name | caseUcfirst }}(Service):
"""{{ method.title }}"""
{% endif %}
- path = '{{ method.path }}'
+ api_path = '{{ method.path }}'
{{ include('python/base/params.twig') }}
{% if 'multipart/form-data' in method.consumes %}
{{ include('python/base/requests/file.twig') }}
diff --git a/templates/python/requirements.txt.twig b/templates/python/requirements.txt.twig
index bf0d9d411..2c24336eb 100644
--- a/templates/python/requirements.txt.twig
+++ b/templates/python/requirements.txt.twig
@@ -1 +1 @@
-requests==2.28.2
\ No newline at end of file
+requests==2.31.0
diff --git a/templates/python/setup.cfg.twig b/templates/python/setup.cfg.twig
index 224a77957..0f94f377b 100644
--- a/templates/python/setup.cfg.twig
+++ b/templates/python/setup.cfg.twig
@@ -1,2 +1,2 @@
[metadata]
-description-file = README.md
\ No newline at end of file
+description_file = README.md
\ No newline at end of file
diff --git a/templates/ruby/base/params.twig b/templates/ruby/base/params.twig
index 8483a2bf4..8276ff6d4 100644
--- a/templates/ruby/base/params.twig
+++ b/templates/ruby/base/params.twig
@@ -1,4 +1,4 @@
- path = '{{ method.path }}'
+ api_path = '{{ method.path }}'
{% for parameter in method.parameters.path %}
.gsub('{{ '{' }}{{ parameter.name }}{{ '}' }}', {{ parameter.name | caseSnake | escapeKeyword }})
{% endfor %}
@@ -11,13 +11,13 @@
{% endif %}
{% endfor %}
- params = {
+ api_params = {
{% for parameter in method.parameters.query | merge(method.parameters.body) %}
{{ parameter.name }}: {{ parameter.name | caseSnake | escapeKeyword }},
{% endfor %}
}
- headers = {
+ api_headers = {
{% for parameter in method.parameters.header %}
"{{ parameter.name }}": {{ parameter.name | caseCamel | escapeKeyword }},
{% endfor %}
diff --git a/templates/ruby/base/requests/api.twig b/templates/ruby/base/requests/api.twig
index 0ac82da49..312e078e8 100644
--- a/templates/ruby/base/requests/api.twig
+++ b/templates/ruby/base/requests/api.twig
@@ -1,8 +1,8 @@
@client.call(
method: '{{ method.method | caseUpper }}',
- path: path,
- headers: headers,
- params: params,
+ path: api_path,
+ headers: api_headers,
+ params: api_params,
{% if method.responseModel and method.responseModel != 'any' %}
response_type: Models::{{method.responseModel | caseUcfirst}}
{% endif %}
diff --git a/templates/ruby/base/requests/file.twig b/templates/ruby/base/requests/file.twig
index f7dcb443e..fda9ca23c 100644
--- a/templates/ruby/base/requests/file.twig
+++ b/templates/ruby/base/requests/file.twig
@@ -7,9 +7,9 @@
{% endif %}
{% endfor %}
@client.chunked_upload(
- path: path,
- headers: headers,
- params: params,
+ path: api_path,
+ headers: api_headers,
+ params: api_params,
param_name: param_name,
id_param_name: id_param_name,
on_progress: on_progress,
diff --git a/templates/ruby/lib/container/client.rb.twig b/templates/ruby/lib/container/client.rb.twig
index fc0762dc9..4b4892889 100644
--- a/templates/ruby/lib/container/client.rb.twig
+++ b/templates/ruby/lib/container/client.rb.twig
@@ -141,7 +141,7 @@ module {{ spec.title | caseUcfirst }}
params: {}
)
chunks_uploaded = current['chunksUploaded'].to_i
- offset = [size, (chunks_uploaded * @chunk_size)].min
+ offset = chunks_uploaded * @chunk_size
end
while offset < size
@@ -158,7 +158,7 @@ module {{ spec.title | caseUcfirst }}
mime_type: input_file.mime_type
)
- headers['content-range'] = "bytes #{offset}-#{[offset + @chunk_size - 1, size].min}/#{size}"
+ headers['content-range'] = "bytes #{offset}-#{[offset + @chunk_size - 1, size - 1].min}/#{size}"
result = call(
method: 'POST',
diff --git a/templates/ruby/lib/container/query.rb.twig b/templates/ruby/lib/container/query.rb.twig
index 00806943f..e62ee4b92 100644
--- a/templates/ruby/lib/container/query.rb.twig
+++ b/templates/ruby/lib/container/query.rb.twig
@@ -34,7 +34,7 @@ module {{spec.title | caseUcfirst}}
end
def between(attribute, start, ending)
- return add_query(attribute, "between", [start, ending])
+ return "between(\"#{attribute}\", #{parse_values(start)}, #{parse_values(ending)})"
end
def starts_with(attribute, value)
@@ -81,13 +81,13 @@ module {{spec.title | caseUcfirst}}
def add_query(attribute, method, value)
if value.is_a?(Array)
- "#{method}(\"#{attribute}\", [#{value.map {|item| parseValues(item)}.join(',')}])"
+ "#{method}(\"#{attribute}\", [#{value.map {|item| parse_values(item)}.join(',')}])"
else
- return "#{method}(\"#{attribute}\", [#{parseValues(value)}])"
+ return "#{method}(\"#{attribute}\", [#{parse_values(value)}])"
end
end
- def parseValues(value)
+ def parse_values(value)
return value.is_a?(String) ? "\"#{value}\"" : value
end
end
diff --git a/templates/ruby/lib/container/role.rb.twig b/templates/ruby/lib/container/role.rb.twig
index 6333fbd64..c3cf9f23d 100644
--- a/templates/ruby/lib/container/role.rb.twig
+++ b/templates/ruby/lib/container/role.rb.twig
@@ -1,9 +1,26 @@
module {{spec.title | caseUcfirst}}
+
+ # Helper class to generate role strings for `Permission`.
class Role
+
+ # Grants access to anyone.
+ #
+ # This includes authenticated and unauthenticated users.
+ #
+ # @return [String]
def self.any
'any'
end
+ # Grants access to a specific user by user ID.
+ #
+ # You can optionally pass verified or unverified for
+ # `status` to target specific types of users.
+ #
+ # @param [String] id
+ # @param [String] status
+ #
+ # @return [String]
def self.user(id, status = "")
if(status.empty?)
"user:#{id}"
@@ -12,6 +29,14 @@ module {{spec.title | caseUcfirst}}
end
end
+ # Grants access to any authenticated or anonymous user.
+ #
+ # You can optionally pass verified or unverified for
+ # `status` to target specific types of users.
+ #
+ # @param [String] status
+ #
+ # @return [String]
def self.users(status = "")
if(status.empty?)
'users'
@@ -20,10 +45,24 @@ module {{spec.title | caseUcfirst}}
end
end
+ # Grants access to any guest user without a session.
+ #
+ # Authenticated users don't have access to this role.
+ #
+ # @return [String]
def self.guests
'guests'
end
+ # Grants access to a team by team ID.
+ #
+ # You can optionally pass a role for `role` to target
+ # team members with the specified role.
+ #
+ # @param [String] id
+ # @param [String] role
+ #
+ # @return [String]
def self.team(id, role = "")
if(role.empty?)
"team:#{id}"
@@ -32,8 +71,25 @@ module {{spec.title | caseUcfirst}}
end
end
+ # Grants access to a specific member of a team.
+ #
+ # When the member is removed from the team, they will
+ # no longer have access.
+ #
+ # @param [String] id
+ #
+ # @return [String]
def self.member(id)
"member:#{id}"
end
+
+ # Grants access to a user with the specified label.
+ #
+ # @param [String] name
+ #
+ # @return [String]
+ def self.label(name)
+ "label:#{name}"
+ end
end
end
\ No newline at end of file
diff --git a/templates/swift/Sources/Client.swift.twig b/templates/swift/Sources/Client.swift.twig
index db36c208f..8be157d3a 100644
--- a/templates/swift/Sources/Client.swift.twig
+++ b/templates/swift/Sources/Client.swift.twig
@@ -301,18 +301,21 @@ open class Client {
default:
var message = ""
var data = try await response.body.collect(upTo: Int.max)
+ var type = ""
do {
let dict = try JSONSerialization.jsonObject(with: data) as? [String: Any]
message = dict?["message"] as? String ?? response.status.reasonPhrase
+ type = dict?["type"] as? String ?? ""
} catch {
message = data.readString(length: data.readableBytes)!
}
throw {{ spec.title | caseUcfirst }}Error(
message: message,
- code: Int(response.status.code)
+ code: Int(response.status.code),
+ type: type
)
}
}
@@ -379,7 +382,7 @@ open class Client {
converter: { return $0 as! [String: Any] }
)
let chunksUploaded = map["chunksUploaded"] as! Int
- offset = min(size, (chunksUploaded * Client.chunkSize))
+ offset = chunksUploaded * Client.chunkSize
} catch {
// File does not exist yet, swallow exception
}
@@ -390,7 +393,7 @@ open class Client {
?? (input.data as! ByteBuffer).getSlice(at: offset, length: Int(size - offset))
params[paramName] = InputFile.fromBuffer(slice!, filename: input.filename, mimeType: input.mimeType)
- headers["content-range"] = "bytes \(offset)-\(min((offset + Client.chunkSize) - 1, size))/\(size)"
+ headers["content-range"] = "bytes \(offset)-\(min((offset + Client.chunkSize) - 1, size - 1))/\(size)"
result = try await call(
method: "POST",
diff --git a/templates/swift/Sources/Query.swift.twig b/templates/swift/Sources/Query.swift.twig
index 9fa9893c7..ce1415d75 100644
--- a/templates/swift/Sources/Query.swift.twig
+++ b/templates/swift/Sources/Query.swift.twig
@@ -33,15 +33,15 @@ public class Query {
}
public static func between(_ attribute: String, start: Int, end: Int) -> String {
- buildQueryWhere(attribute, is: "between", to: [start, end])
+ "between(\"\(attribute)\", \(start), \(end))"
}
public static func between(_ attribute: String, start: Double, end: Double) -> String {
- buildQueryWhere(attribute, is: "between", to: [start, end])
+ "between(\"\(attribute)\", \(start), \(end))"
}
public static func between(_ attribute: String, start: String, end: String) -> String {
- buildQueryWhere(attribute, is: "between", to: [start, end])
+ "between(\"\(attribute)\", \"\(start)\", \"\(end)\")"
}
public static func startsWith(_ attribute: String, value: String) -> String {
diff --git a/templates/swift/Sources/Role.swift.twig b/templates/swift/Sources/Role.swift.twig
index b8acdbb32..1e8e755f5 100644
--- a/templates/swift/Sources/Role.swift.twig
+++ b/templates/swift/Sources/Role.swift.twig
@@ -1,8 +1,21 @@
+/// Helper class to generate role strings for `Permission`.
public class Role {
+
+ /// Grants access to anyone.
+ ///
+ /// This includes authenticated and unauthenticated users.
public static func any() -> String {
return "any"
}
+ /// Grants access to a specific user by user ID.
+ ///
+ /// You can optionally pass verified or unverified for
+ /// `status` to target specific types of users.
+ ///
+ /// @param String id
+ /// @param String status
+ /// @return String
public static func user(_ id: String, _ status: String = "") -> String {
if(status.isEmpty) {
return "user:\(id)"
@@ -10,6 +23,13 @@ public class Role {
return "user:\(id)/\(status)"
}
+ /// Grants access to any authenticated or anonymous user.
+ ///
+ /// You can optionally pass verified or unverified for
+ /// `status` to target specific types of users.
+ ///
+ /// @param String status
+ /// @return String
public static func users(_ status: String = "") -> String {
if(status.isEmpty) {
return "users"
@@ -17,10 +37,23 @@ public class Role {
return "users/\(status)"
}
+ /// Grants access to any guest user without a session.
+ ///
+ /// Authenticated users don't have access to this role.
+ ///
+ /// @return String
public static func guests() -> String {
return "guests"
}
+ /// Grants access to a team by team ID.
+ ///
+ /// You can optionally pass a role for `role` to target
+ /// team members with the specified role.
+ ///
+ /// @param String id
+ /// @param String role
+ /// @return String
public static func team(_ id: String, _ role: String = "") -> String {
if(role.isEmpty) {
return "team:\(id)"
@@ -28,7 +61,22 @@ public class Role {
return "team:\(id)/\(role)"
}
+ /// Grants access to a specific member of a team.
+ ///
+ /// When the member is removed from the team, they will
+ /// no longer have access.
+ ///
+ /// @param String id
+ /// @return String
public static func member(_ id: String) -> String {
return "member:\(id)"
}
+
+ /// Grants access to a user with the specified label.
+ ///
+ /// @param String name
+ /// @return String
+ public static func label(_ name: String) -> String {
+ return "label:\(name)"
+ }
}
\ No newline at end of file
diff --git a/templates/swift/Sources/Services/Service.swift.twig b/templates/swift/Sources/Services/Service.swift.twig
index 7c581f5db..aad435162 100644
--- a/templates/swift/Sources/Services/Service.swift.twig
+++ b/templates/swift/Sources/Services/Service.swift.twig
@@ -35,19 +35,19 @@ open class {{ service.name | caseUcfirst }}: Service {
{%~ if 'multipart/form-data' in method.consumes %}
onProgress: ((UploadProgress) -> Void)? = nil
{%~ endif %}
- ){%~ if method.type != "webAuth" %} async{% endif %} throws -> {{ method | returnType(spec) | raw }} {
+ ) async throws -> {{ method | returnType(spec) | raw }} {
{{~ include('swift/base/params.twig') }}
{%~ if method.type == 'webAuth' %}
- {{~ include('swift/base/requests/OAuth.twig') }}
+ {{~ include('swift/base/requests/oauth.twig') }}
{%~ elseif method.type == 'location' %}
{{~ include('swift/base/requests/location.twig')}}
{%~ else %}
{%~ if method.headers | length <= 0 %}
- let headers: [String: String] = [:]
+ let apiHeaders: [String: String] = [:]
{%~ else %}
{% if 'multipart/form-data' in method.consumes -%} var
{%- else -%} let
- {%- endif %} headers: [String: String] = [
+ {%- endif %} apiHeaders: [String: String] = [
{%~ for key, header in method.headers %}
"{{ key }}": "{{ header }}"{% if not loop.last %},{% endif %}
diff --git a/templates/swift/base/params.twig b/templates/swift/base/params.twig
index 4fe11e5aa..518090ea8 100644
--- a/templates/swift/base/params.twig
+++ b/templates/swift/base/params.twig
@@ -1,14 +1,14 @@
- let path: String = "{{ method.path }}"
+ let apiPath: String = "{{ method.path }}"
{%~ for parameter in method.parameters.path %}
.replacingOccurrences(of: "{{ '{' }}{{ parameter.name | caseCamel }}{{ '}' }}", with: {{ parameter.name | caseCamel | escapeKeyword }})
{%~ endfor %}
{%~ if method.parameters.query | merge(method.parameters.body) | length <= 0 %}
- let params: [String: Any] = [:]
+ let apiParams: [String: Any] = [:]
{%~ else %}
{% if 'multipart/form-data' in method.consumes -%} var
{%- else -%} let
- {%- endif %} params: [String: Any?] = [
+ {%- endif %} apiParams: [String: Any?] = [
{%~ for parameter in method.parameters.query | merge(method.parameters.body) %}
"{{ parameter.name }}": {{ parameter.name | caseCamel | escapeKeyword }}{% if not loop.last or (method.type == 'location' or method.type == 'webAuth' and method.auth | length > 0) %},{% endif %}
diff --git a/templates/swift/base/requests/OAuth.twig b/templates/swift/base/requests/OAuth.twig
deleted file mode 100644
index a0d0b5c7a..000000000
--- a/templates/swift/base/requests/OAuth.twig
+++ /dev/null
@@ -1,12 +0,0 @@
- let query = "?\(client.parametersToQueryString(params: params))"
- let url = URL(string: client.endPoint + path + query)!
- let callbackScheme = "appwrite-callback-\(client.config["project"] ?? "")"
- let group = DispatchGroup()
-
- group.enter()
- WebAuthComponent.authenticate(url: url, callbackScheme: callbackScheme) { result in
- group.leave()
- }
- group.wait()
-
- return true
diff --git a/templates/swift/base/requests/api.twig b/templates/swift/base/requests/api.twig
index 1798b60bc..e93264245 100644
--- a/templates/swift/base/requests/api.twig
+++ b/templates/swift/base/requests/api.twig
@@ -1,8 +1,8 @@
return try await client.call(
method: "{{ method.method | caseUpper }}",
- path: path,
- headers: headers,
- params: params{% if method.responseModel %},
+ path: apiPath,
+ headers: apiHeaders,
+ params: apiParams{% if method.responseModel %},
converter: converter
{% endif %}
)
\ No newline at end of file
diff --git a/templates/swift/base/requests/file.twig b/templates/swift/base/requests/file.twig
index 594bbe38b..2167704ec 100644
--- a/templates/swift/base/requests/file.twig
+++ b/templates/swift/base/requests/file.twig
@@ -6,9 +6,9 @@
{% endif %}
{% endfor %}
return try await client.chunkedUpload(
- path: path,
- headers: &headers,
- params: ¶ms,
+ path: apiPath,
+ headers: &apiHeaders,
+ params: &apiParams,
paramName: paramName,
idParamName: idParamName,
{% if method.responseModel %}
diff --git a/templates/swift/base/requests/location.twig b/templates/swift/base/requests/location.twig
index a115d056d..78d4b1d1a 100644
--- a/templates/swift/base/requests/location.twig
+++ b/templates/swift/base/requests/location.twig
@@ -1,5 +1,5 @@
return try await client.call(
method: "{{ method.method | caseUpper }}",
- path: path,
- params: params
+ path: apiPath,
+ params: apiParams
)
\ No newline at end of file
diff --git a/templates/swift/base/requests/oauth.twig b/templates/swift/base/requests/oauth.twig
new file mode 100644
index 000000000..02174947a
--- /dev/null
+++ b/templates/swift/base/requests/oauth.twig
@@ -0,0 +1,11 @@
+ let query = "?\(client.parametersToQueryString(params: apiParams))"
+ let url = URL(string: client.endPoint + apiPath + query)!
+ let callbackScheme = "appwrite-callback-\(client.config["project"] ?? "")"
+
+ try await withCheckedThrowingContinuation { continuation in
+ WebAuthComponent.authenticate(url: url, callbackScheme: callbackScheme) { result in
+ continuation.resume(with: result)
+ }
+ }
+
+ return true
diff --git a/templates/web/src/query.ts.twig b/templates/web/src/query.ts.twig
index 51cbb7a9f..1f880cb1c 100644
--- a/templates/web/src/query.ts.twig
+++ b/templates/web/src/query.ts.twig
@@ -28,7 +28,7 @@ export class Query {
`isNotNull("${attribute}")`;
static between = (attribute: string, start: string|number, end: string|number): string =>
- `between("${attribute}", [${Query.parseValues(start)},${Query.parseValues(end)}])`;
+ `between("${attribute}", ${Query.parseValues(start)}, ${Query.parseValues(end)})`;
static startsWith = (attribute: string, value: string): string =>
Query.addQuery(attribute, "startsWith", value);
diff --git a/templates/web/src/role.ts.twig b/templates/web/src/role.ts.twig
index d94e398c6..79f8c6b62 100644
--- a/templates/web/src/role.ts.twig
+++ b/templates/web/src/role.ts.twig
@@ -1,34 +1,100 @@
+/**
+ * Helper class to generate role strings for `Permission`.
+ */
export class Role {
+
+ /**
+ * Grants access to anyone.
+ *
+ * This includes authenticated and unauthenticated users.
+ *
+ * @returns {string}
+ */
public static any(): string {
return 'any'
}
+ /**
+ * Grants access to a specific user by user ID.
+ *
+ * You can optionally pass verified or unverified for
+ * `status` to target specific types of users.
+ *
+ * @param {string} id
+ * @param {string} status
+ * @returns {string}
+ */
public static user(id: string, status: string = ''): string {
- if(status === '') {
+ if (status === '') {
return `user:${id}`
}
return `user:${id}/${status}`
}
-
+
+ /**
+ * Grants access to any authenticated or anonymous user.
+ *
+ * You can optionally pass verified or unverified for
+ * `status` to target specific types of users.
+ *
+ * @param {string} status
+ * @returns {string}
+ */
public static users(status: string = ''): string {
- if(status === '') {
+ if (status === '') {
return 'users'
}
return `users/${status}`
}
-
+
+ /**
+ * Grants access to any guest user without a session.
+ *
+ * Authenticated users don't have access to this role.
+ *
+ * @returns {string}
+ */
public static guests(): string {
return 'guests'
}
-
+
+ /**
+ * Grants access to a team by team ID.
+ *
+ * You can optionally pass a role for `role` to target
+ * team members with the specified role.
+ *
+ * @param {string} id
+ * @param {string} role
+ * @returns {string}
+ */
public static team(id: string, role: string = ''): string {
- if(role === '') {
+ if (role === '') {
return `team:${id}`
}
return `team:${id}/${role}`
}
+ /**
+ * Grants access to a specific member of a team.
+ *
+ * When the member is removed from the team, they will
+ * no longer have access.
+ *
+ * @param {string} id
+ * @returns {string}
+ */
public static member(id: string): string {
return `member:${id}`
}
+
+ /**
+ * Grants access to a user with the specified label.
+ *
+ * @param {string} name
+ * @returns {string}
+ */
+ public static label(name: string): string {
+ return `label:${name}`
+ }
}
\ No newline at end of file
diff --git a/templates/web/src/services/template.ts.twig b/templates/web/src/services/template.ts.twig
index cd7ddb88a..585e5de60 100644
--- a/templates/web/src/services/template.ts.twig
+++ b/templates/web/src/services/template.ts.twig
@@ -34,8 +34,8 @@ export class {{ service.name | caseUcfirst }} extends Service {
{% endif %}
{% endfor %}
- let path = '{{ method.path }}'{% for parameter in method.parameters.path %}.replace('{{ '{' }}{{ parameter.name | caseCamel | escapeKeyword }}{{ '}' }}', {{ parameter.name | caseCamel | escapeKeyword }}){% endfor %};
- let payload: Payload = {};
+ const apiPath = '{{ method.path }}'{% for parameter in method.parameters.path %}.replace('{{ '{' }}{{ parameter.name | caseCamel | escapeKeyword }}{{ '}' }}', {{ parameter.name | caseCamel | escapeKeyword }}){% endfor %};
+ const payload: Payload = {};
{% for parameter in method.parameters.query %}
if (typeof {{ parameter.name | caseCamel | escapeKeyword }} !== 'undefined') {
@@ -49,7 +49,7 @@ export class {{ service.name | caseUcfirst }} extends Service {
}
{% endfor %}
- const uri = new URL(this.client.config.endpoint + path);
+ const uri = new URL(this.client.config.endpoint + apiPath);
{% if method.type == 'location' or method.type == 'webAuth' %}
{% if method.auth|length > 0 %}
{% for node in method.auth %}
@@ -85,7 +85,6 @@ export class {{ service.name | caseUcfirst }} extends Service {
if (size <= Service.CHUNK_SIZE) {
return await this.client.call('{{ method.method | caseLower }}', uri, {
-
{% for parameter in method.parameters.header %}
'{{ parameter.name | caseCamel | escapeKeyword }}': this.client.${{ parameter.name | caseCamel | escapeKeyword }},
{% endfor %}
@@ -94,10 +93,8 @@ export class {{ service.name | caseUcfirst }} extends Service {
{% endfor %}
}, payload);
}
- let id = undefined;
- let response = undefined;
- const headers: { [header: string]: string } = {
+ const apiHeaders: { [header: string]: string } = {
{% for parameter in method.parameters.header %}
'{{ parameter.name | caseCamel | escapeKeyword }}': this.client.${{ parameter.name | caseCamel | escapeKeyword }},
{% endfor %}
@@ -106,50 +103,43 @@ export class {{ service.name | caseUcfirst }} extends Service {
{% endfor %}
}
- let counter = 0;
- const totalCounters = Math.ceil(size / Service.CHUNK_SIZE);
+ let offset = 0;
+ let response = undefined;
{% for parameter in method.parameters.all %}
{% if parameter.isUploadID %}
if({{ parameter.name | caseCamel | escapeKeyword }} != 'unique()') {
try {
- response = await this.client.call('GET', new URL(this.client.config.endpoint + path + '/' + {{ parameter.name }}), headers);
- counter = response.chunksUploaded;
+ response = await this.client.call('GET', new URL(this.client.config.endpoint + apiPath + '/' + {{ parameter.name }}), apiHeaders);
+ offset = response.chunksUploaded * Service.CHUNK_SIZE;
} catch(e) {
}
}
{% endif %}
{% endfor %}
- for (counter; counter < totalCounters; counter++) {
- const start = (counter * Service.CHUNK_SIZE);
- const end = Math.min((((counter * Service.CHUNK_SIZE) + Service.CHUNK_SIZE) - 1), size);
-
- headers['content-range'] = 'bytes ' + start + '-' + end + '/' + size
+ while (offset < size) {
+ let end = Math.min(offset + Service.CHUNK_SIZE - 1, size - 1);
- if (id) {
- headers['x-{{spec.title | caseLower }}-id'] = id;
+ apiHeaders['content-range'] = 'bytes ' + offset + '-' + end + '/' + size;
+ if (response && response.$id) {
+ apiHeaders['x-{{spec.title | caseLower }}-id'] = response.$id;
}
- const stream = {{ parameter.name | caseCamel | escapeKeyword }}.slice(start, end + 1);
- payload['{{ parameter.name }}'] = new File([stream], {{ parameter.name | caseCamel | escapeKeyword }}.name);
-
- response = await this.client.call('{{ method.method | caseLower }}', uri, headers, payload);
-
- if (!id) {
- id = response['$id'];
- }
+ const chunk = {{ parameter.name | caseCamel | escapeKeyword }}.slice(offset, end + 1);
+ payload['{{ parameter.name }}'] = new File([chunk], {{ parameter.name | caseCamel | escapeKeyword }}.name);
+ response = await this.client.call('{{ method.method | caseLower }}', uri, apiHeaders, payload);
if (onProgress) {
onProgress({
$id: response.$id,
- progress: Math.min((counter + 1) * Service.CHUNK_SIZE - 1, size) / size * 100,
- sizeUploaded: end,
+ progress: (offset / size) * 100,
+ sizeUploaded: offset,
chunksTotal: response.chunksTotal,
chunksUploaded: response.chunksUploaded
});
}
+ offset += Service.CHUNK_SIZE;
}
-
return response;
{% endif %}
{% endfor %}
diff --git a/tests/Android11Java11Test.php b/tests/Android11Java11Test.php
index 3985cd3f2..9aa7a6c1a 100644
--- a/tests/Android11Java11Test.php
+++ b/tests/Android11Java11Test.php
@@ -17,7 +17,7 @@ class Android11Java11Test extends Base
'chmod +x tests/sdks/android/gradlew',
];
protected string $command =
- 'docker run --rm -v $(pwd):/app -w /app/tests/sdks/android alvrme/alpine-android:android-30-jdk11 sh -c "./gradlew :library:testReleaseUnitTest -q && cat library/result.txt"';
+ 'docker run --network="mockapi" --rm -v $(pwd):/app -w /app/tests/sdks/android alvrme/alpine-android:android-30-jdk11 sh -c "./gradlew :library:testReleaseUnitTest --stacktrace -q && cat library/result.txt"';
protected array $expectedOutput = [
...Base::FOO_RESPONSES,
@@ -28,7 +28,7 @@ class Android11Java11Test extends Base
...Base::LARGE_FILE_RESPONSES,
...Base::EXCEPTION_RESPONSES,
...Base::REALTIME_RESPONSES,
- ...Base::COOKIE_RESPONSES,
+ // ...Base::COOKIE_RESPONSES,
...Base::QUERY_HELPER_RESPONSES,
...Base::PERMISSION_HELPER_RESPONSES,
...Base::ID_HELPER_RESPONSES
diff --git a/tests/Android11Java8Test.php b/tests/Android11Java8Test.php
index a73b040ad..e79656ee8 100644
--- a/tests/Android11Java8Test.php
+++ b/tests/Android11Java8Test.php
@@ -17,7 +17,7 @@ class Android11Java8Test extends Base
'chmod +x tests/sdks/android/gradlew',
];
protected string $command =
- 'docker run --rm -v $(pwd):/app -w /app/tests/sdks/android alvrme/alpine-android:android-30-jdk8 sh -c "./gradlew :library:testReleaseUnitTest -q && cat library/result.txt"';
+ 'docker run --network="mockapi" --rm -v $(pwd):/app -w /app/tests/sdks/android alvrme/alpine-android:android-30-jdk8 sh -c "./gradlew :library:testReleaseUnitTest --stacktrace -q && cat library/result.txt"';
protected array $expectedOutput = [
...Base::FOO_RESPONSES,
@@ -28,7 +28,7 @@ class Android11Java8Test extends Base
...Base::LARGE_FILE_RESPONSES,
...Base::EXCEPTION_RESPONSES,
...Base::REALTIME_RESPONSES,
- ...Base::COOKIE_RESPONSES,
+ // ...Base::COOKIE_RESPONSES,
...Base::QUERY_HELPER_RESPONSES,
...Base::PERMISSION_HELPER_RESPONSES,
...Base::ID_HELPER_RESPONSES
diff --git a/tests/Android12Java11Test.php b/tests/Android12Java11Test.php
index 23eec2fbb..aead05eab 100644
--- a/tests/Android12Java11Test.php
+++ b/tests/Android12Java11Test.php
@@ -17,7 +17,7 @@ class Android12Java11Test extends Base
'chmod +x tests/sdks/android/gradlew',
];
protected string $command =
- 'docker run --rm -v $(pwd):/app -w /app/tests/sdks/android alvrme/alpine-android:android-32-jdk11 sh -c "./gradlew :library:testReleaseUnitTest -q && cat library/result.txt"';
+ 'docker run --network="mockapi" --rm -v $(pwd):/app -w /app/tests/sdks/android alvrme/alpine-android:android-32-jdk11 sh -c "./gradlew :library:testReleaseUnitTest --stacktrace -q && cat library/result.txt"';
protected array $expectedOutput = [
...Base::FOO_RESPONSES,
@@ -28,7 +28,7 @@ class Android12Java11Test extends Base
...Base::LARGE_FILE_RESPONSES,
...Base::EXCEPTION_RESPONSES,
...Base::REALTIME_RESPONSES,
- ...Base::COOKIE_RESPONSES,
+ // ...Base::COOKIE_RESPONSES,
...Base::QUERY_HELPER_RESPONSES,
...Base::PERMISSION_HELPER_RESPONSES,
...Base::ID_HELPER_RESPONSES
diff --git a/tests/Android12Java8Test.php b/tests/Android12Java8Test.php
index 8718b25b4..1fe8f5340 100644
--- a/tests/Android12Java8Test.php
+++ b/tests/Android12Java8Test.php
@@ -17,7 +17,7 @@ class Android12Java8Test extends Base
'chmod +x tests/sdks/android/gradlew',
];
protected string $command =
- 'docker run --rm -v $(pwd):/app -w /app/tests/sdks/android alvrme/alpine-android:android-32-jdk8 sh -c "./gradlew :library:testReleaseUnitTest -q && cat library/result.txt"';
+ 'docker run --network="mockapi" --rm -v $(pwd):/app -w /app/tests/sdks/android alvrme/alpine-android:android-32-jdk8 sh -c "./gradlew :library:testReleaseUnitTest --stacktrace -q && cat library/result.txt"';
protected array $expectedOutput = [
...Base::FOO_RESPONSES,
@@ -28,7 +28,7 @@ class Android12Java8Test extends Base
...Base::LARGE_FILE_RESPONSES,
...Base::EXCEPTION_RESPONSES,
...Base::REALTIME_RESPONSES,
- ...Base::COOKIE_RESPONSES,
+ // ...Base::COOKIE_RESPONSES,
...Base::QUERY_HELPER_RESPONSES,
...Base::PERMISSION_HELPER_RESPONSES,
...Base::ID_HELPER_RESPONSES
diff --git a/tests/Android5Java11Test.php b/tests/Android5Java11Test.php
index 0b29aff87..a0861181a 100644
--- a/tests/Android5Java11Test.php
+++ b/tests/Android5Java11Test.php
@@ -17,7 +17,7 @@ class Android5Java11Test extends Base
'chmod +x tests/sdks/android/gradlew',
];
protected string $command =
- 'docker run --rm -v $(pwd):/app -w /app/tests/sdks/android alvrme/alpine-android:android-21-jdk11 sh -c "./gradlew :library:testReleaseUnitTest -q && cat library/result.txt"';
+ 'docker run --network="mockapi" --rm -v $(pwd):/app -w /app/tests/sdks/android alvrme/alpine-android:android-21-jdk11 sh -c "./gradlew :library:testReleaseUnitTest --stacktrace -q && cat library/result.txt"';
protected array $expectedOutput = [
...Base::FOO_RESPONSES,
@@ -28,7 +28,7 @@ class Android5Java11Test extends Base
...Base::LARGE_FILE_RESPONSES,
...Base::EXCEPTION_RESPONSES,
...Base::REALTIME_RESPONSES,
- ...Base::COOKIE_RESPONSES,
+ // ...Base::COOKIE_RESPONSES,
...Base::QUERY_HELPER_RESPONSES,
...Base::PERMISSION_HELPER_RESPONSES,
...Base::ID_HELPER_RESPONSES
diff --git a/tests/Android5Java8Test.php b/tests/Android5Java8Test.php
index 6332f1040..5d80aa266 100644
--- a/tests/Android5Java8Test.php
+++ b/tests/Android5Java8Test.php
@@ -17,7 +17,7 @@ class Android5Java8Test extends Base
'chmod +x tests/sdks/android/gradlew',
];
protected string $command =
- 'docker run --rm -v $(pwd):/app -w /app/tests/sdks/android alvrme/alpine-android:android-21-jdk8 sh -c "./gradlew :library:testReleaseUnitTest -q && cat library/result.txt"';
+ 'docker run --network="mockapi" --rm -v $(pwd):/app -w /app/tests/sdks/android alvrme/alpine-android:android-21-jdk8 sh -c "./gradlew :library:testReleaseUnitTest --stacktrace -q && cat library/result.txt"';
protected array $expectedOutput = [
...Base::FOO_RESPONSES,
@@ -28,7 +28,7 @@ class Android5Java8Test extends Base
...Base::LARGE_FILE_RESPONSES,
...Base::EXCEPTION_RESPONSES,
...Base::REALTIME_RESPONSES,
- ...Base::COOKIE_RESPONSES,
+ // ...Base::COOKIE_RESPONSES,
...Base::QUERY_HELPER_RESPONSES,
...Base::PERMISSION_HELPER_RESPONSES,
...Base::ID_HELPER_RESPONSES
diff --git a/tests/AppleSwift55Test.php b/tests/AppleSwift55Test.php
index b9b2e8842..4cbd655a0 100644
--- a/tests/AppleSwift55Test.php
+++ b/tests/AppleSwift55Test.php
@@ -16,7 +16,7 @@ class AppleSwift55Test extends Base
'cp tests/languages/apple/Tests.swift tests/sdks/apple/Tests/AppwriteTests/Tests.swift',
];
protected string $command =
- 'docker run --rm -v $(pwd):/app -w /app/tests/sdks/apple swiftarm/swift:5.5.2-focal-multi-arch swift test';
+ 'docker run --network="mockapi" --rm -v $(pwd):/app -w /app/tests/sdks/apple swiftarm/swift:5.5.2-focal-multi-arch swift test';
protected array $expectedOutput = [
...Base::FOO_RESPONSES,
diff --git a/tests/Base.php b/tests/Base.php
index 2df5b08cf..e0855595b 100644
--- a/tests/Base.php
+++ b/tests/Base.php
@@ -69,9 +69,9 @@ abstract class Base extends TestCase
'search("name", ["john"])',
'isNull("name")',
'isNotNull("name")',
- 'between("age", [50,100])',
- 'between("age", [50.5,100.5])',
- 'between("name", ["Anna","Brad"])',
+ 'between("age", 50, 100)',
+ 'between("age", 50.5, 100.5)',
+ 'between("name", "Anna", "Brad")',
'startsWith("name", ["Ann"])',
'endsWith("name", ["nne"])',
'select(["name","age"])',
@@ -93,6 +93,7 @@ abstract class Base extends TestCase
'create("member:memberId")',
'update("users/verified")',
'update("user:userid/unverified")',
+ 'create("label:admin")',
];
protected const ID_HELPER_RESPONSES = [
@@ -105,11 +106,23 @@ abstract class Base extends TestCase
protected array $build = [];
protected string $command = '';
protected array $expectedOutput = [];
+ protected string $sdkName;
+ protected string $sdkPlatform;
+ protected string $sdkLanguage;
+ protected string $version;
public function setUp(): void
{
$headers = "x-sdk-name: {$this->sdkName}; x-sdk-platform: {$this->sdkPlatform}; x-sdk-language: {$this->sdkLanguage}; x-sdk-version: {$this->version}";
array_push($this->expectedOutput, $headers);
+
+ // Figure out if mock-server is running
+ $isMockAPIRunning = (strlen(exec('docker ps | grep mock-server')) > 0);
+
+ if (!$isMockAPIRunning) {
+ echo "Starting Mock API Server";
+ exec('cd ./mock-server && docker-compose up -d --force-recreate');
+ }
}
public function tearDown(): void
diff --git a/tests/CLINode14Test.php b/tests/CLINode14Test.php
index 9986758d1..6b92eb423 100644
--- a/tests/CLINode14Test.php
+++ b/tests/CLINode14Test.php
@@ -19,7 +19,7 @@ class CLINode14Test extends Base
'cp tests/languages/cli/test.js tests/sdks/cli/test.js'
];
protected string $command =
- 'docker run --rm -v $(pwd):/app -w /app/tests/sdks/cli node:14-alpine node test.js';
+ 'docker run --network="mockapi" --rm -v $(pwd):/app -w /app/tests/sdks/cli node:14-alpine node test.js';
protected array $expectedOutput = [
...Base::FOO_RESPONSES,
diff --git a/tests/CLINode16Test.php b/tests/CLINode16Test.php
index c6ed8549c..0dc378ca4 100644
--- a/tests/CLINode16Test.php
+++ b/tests/CLINode16Test.php
@@ -19,7 +19,7 @@ class CLINode16Test extends Base
'cp tests/languages/cli/test.js tests/sdks/cli/test.js'
];
protected string $command =
- 'docker run --rm -v $(pwd):/app -w /app/tests/sdks/cli node:16-alpine node test.js';
+ 'docker run --network="mockapi" --rm -v $(pwd):/app -w /app/tests/sdks/cli node:16-alpine node test.js';
protected array $expectedOutput = [
...Base::FOO_RESPONSES,
diff --git a/tests/DartBetaTest.php b/tests/DartBetaTest.php
index f6dec74ee..85a8e8b9e 100644
--- a/tests/DartBetaTest.php
+++ b/tests/DartBetaTest.php
@@ -16,7 +16,7 @@ class DartBetaTest extends Base
'cp tests/languages/dart/tests.dart tests/sdks/dart/tests/tests.dart',
];
protected string $command =
- 'docker run --rm -v $(pwd):/app -w /app/tests/sdks/dart dart:beta sh -c "dart pub get && dart pub run tests/tests.dart"';
+ 'docker run --network="mockapi" --rm -v $(pwd):/app -w /app/tests/sdks/dart dart:beta sh -c "dart pub get && dart pub run tests/tests.dart"';
protected array $expectedOutput = [
...Base::FOO_RESPONSES,
diff --git a/tests/DartStableTest.php b/tests/DartStableTest.php
index 1c586ab28..ac21d95d5 100644
--- a/tests/DartStableTest.php
+++ b/tests/DartStableTest.php
@@ -16,7 +16,7 @@ class DartStableTest extends Base
'cp tests/languages/dart/tests.dart tests/sdks/dart/tests/tests.dart',
];
protected string $command =
- 'docker run --rm -v $(pwd):/app -w /app/tests/sdks/dart dart:stable sh -c "dart pub get && dart pub run tests/tests.dart"';
+ 'docker run --network="mockapi" --rm -v $(pwd):/app -w /app/tests/sdks/dart dart:stable sh -c "dart pub get && dart pub run tests/tests.dart"';
protected array $expectedOutput = [
...Base::FOO_RESPONSES,
diff --git a/tests/Deno1193Test.php b/tests/Deno1193Test.php
index 29f5502ca..a16066f85 100644
--- a/tests/Deno1193Test.php
+++ b/tests/Deno1193Test.php
@@ -13,7 +13,7 @@ class Deno1193Test extends Base
protected string $class = 'Appwrite\SDK\Language\Deno';
protected array $build = [];
protected string $command =
- 'docker run --rm -v $(pwd):/app -w /app denoland/deno:alpine-1.19.3 run --allow-net --allow-read tests/languages/deno/tests.ts';
+ 'docker run --network="mockapi" --rm -v $(pwd):/app -w /app denoland/deno:alpine-1.19.3 run --allow-net --allow-read tests/languages/deno/tests.ts';
protected array $expectedOutput = [
...Base::FOO_RESPONSES,
diff --git a/tests/Deno1303Test.php b/tests/Deno1303Test.php
index fb09573b9..ef28031f7 100644
--- a/tests/Deno1303Test.php
+++ b/tests/Deno1303Test.php
@@ -13,7 +13,7 @@ class Deno1303Test extends Base
protected string $class = 'Appwrite\SDK\Language\Deno';
protected array $build = [];
protected string $command =
- 'docker run --rm -v $(pwd):/app -w /app denoland/deno:alpine-1.30.3 run --allow-net --allow-read tests/languages/deno/tests.ts';
+ 'docker run --network="mockapi" --rm -v $(pwd):/app -w /app denoland/deno:alpine-1.30.3 run --allow-net --allow-read tests/languages/deno/tests.ts';
protected array $expectedOutput = [
...Base::FOO_RESPONSES,
diff --git a/tests/DotNet31Test.php b/tests/DotNet31Test.php
index 9d0b6733a..0852909fb 100644
--- a/tests/DotNet31Test.php
+++ b/tests/DotNet31Test.php
@@ -17,7 +17,7 @@ class DotNet31Test extends Base
'cp tests/languages/dotnet/Tests31.csproj tests/sdks/dotnet/src/test/Tests.csproj',
];
protected string $command =
- 'docker run --rm -v $(pwd):/app -w /app/tests/sdks/dotnet/src/test/ mcr.microsoft.com/dotnet/sdk:3.1 dotnet test --verbosity normal --framework netcoreapp3.1';
+ 'docker run --network="mockapi" --rm -v $(pwd):/app -w /app/tests/sdks/dotnet/src/test/ mcr.microsoft.com/dotnet/sdk:3.1 dotnet test --verbosity normal --framework netcoreapp3.1';
protected array $expectedOutput = [
...Base::FOO_RESPONSES,
diff --git a/tests/DotNet60Test.php b/tests/DotNet60Test.php
index ecadcd9d6..d2d74c17d 100644
--- a/tests/DotNet60Test.php
+++ b/tests/DotNet60Test.php
@@ -17,7 +17,7 @@ class DotNet60Test extends Base
'cp tests/languages/dotnet/Tests60.csproj tests/sdks/dotnet/src/test/Tests.csproj',
];
protected string $command =
- 'docker run --rm -v $(pwd):/app -w /app/tests/sdks/dotnet/src/test/ mcr.microsoft.com/dotnet/sdk:6.0-alpine3.17 dotnet test --verbosity normal --framework net6.0';
+ 'docker run --network="mockapi" --rm -v $(pwd):/app -w /app/tests/sdks/dotnet/src/test/ mcr.microsoft.com/dotnet/sdk:6.0-alpine3.17 dotnet test --verbosity normal --framework net6.0';
protected array $expectedOutput = [
...Base::FOO_RESPONSES,
diff --git a/tests/DotNet70Test.php b/tests/DotNet70Test.php
index 582213e81..b73a37713 100644
--- a/tests/DotNet70Test.php
+++ b/tests/DotNet70Test.php
@@ -17,7 +17,7 @@ class DotNet70Test extends Base
'cp tests/languages/dotnet/Tests70.csproj tests/sdks/dotnet/src/test/Tests.csproj',
];
protected string $command =
- 'docker run --rm -v $(pwd):/app -w /app/tests/sdks/dotnet/src/test/ mcr.microsoft.com/dotnet/sdk:7.0-alpine3.17 dotnet test --verbosity normal --framework net7.0';
+ 'docker run --network="mockapi" --rm -v $(pwd):/app -w /app/tests/sdks/dotnet/src/test/ mcr.microsoft.com/dotnet/sdk:7.0-alpine3.17 dotnet test --verbosity normal --framework net7.0';
protected array $expectedOutput = [
...Base::FOO_RESPONSES,
diff --git a/tests/FlutterBetaTest.php b/tests/FlutterBetaTest.php
index bbb1ff2bf..50e4caaee 100644
--- a/tests/FlutterBetaTest.php
+++ b/tests/FlutterBetaTest.php
@@ -16,7 +16,7 @@ class FlutterBetaTest extends Base
'cp tests/languages/flutter/tests.dart tests/sdks/flutter/test/appwrite_test.dart',
];
protected string $command =
- 'docker run --rm -v $(pwd):/app -w /app/tests/sdks/flutter --env PUB_CACHE=vendor cirrusci/flutter:beta sh -c "flutter pub get && flutter test test/appwrite_test.dart"';
+ 'docker run --network="mockapi" --rm -v $(pwd):/app -w /app/tests/sdks/flutter --env PUB_CACHE=vendor cirrusci/flutter:beta sh -c "flutter pub get && flutter test test/appwrite_test.dart"';
protected array $expectedOutput = [
...Base::FOO_RESPONSES,
diff --git a/tests/FlutterStableTest.php b/tests/FlutterStableTest.php
index 342820ddd..1fba442ea 100644
--- a/tests/FlutterStableTest.php
+++ b/tests/FlutterStableTest.php
@@ -16,7 +16,7 @@ class FlutterStableTest extends Base
'cp tests/languages/flutter/tests.dart tests/sdks/flutter/test/appwrite_test.dart',
];
protected string $command =
- 'docker run --rm -v $(pwd):/app -w /app/tests/sdks/flutter --env PUB_CACHE=vendor cirrusci/flutter:stable sh -c "flutter pub get && flutter test test/appwrite_test.dart"';
+ 'docker run --network="mockapi" --rm -v $(pwd):/app -w /app/tests/sdks/flutter --env PUB_CACHE=vendor cirrusci/flutter:stable sh -c "flutter pub get && flutter test test/appwrite_test.dart"';
protected array $expectedOutput = [
...Base::FOO_RESPONSES,
diff --git a/tests/Go112Test.php b/tests/Go112Test.php
index c845e202f..1da698fa2 100644
--- a/tests/Go112Test.php
+++ b/tests/Go112Test.php
@@ -16,7 +16,7 @@ class Go112Test extends Base
'cp -Rf tests/sdks/go/* tests/tmp/go/src/github.com/repoowner/sdk-for-go/'
];
protected string $command =
- 'docker run --rm -v $(pwd):/app -w /app golang:1.12 sh -c "cd tests/languages/go/ && ./test.sh"';
+ 'docker run --network="mockapi" --rm -v $(pwd):/app -w /app golang:1.12 sh -c "cd tests/languages/go/ && ./test.sh"';
protected array $expectedOutput = [
...Base::FOO_RESPONSES,
...Base::BAR_RESPONSES,
diff --git a/tests/Go118Test.php b/tests/Go118Test.php
index d05acf7ac..4f859f434 100644
--- a/tests/Go118Test.php
+++ b/tests/Go118Test.php
@@ -16,7 +16,7 @@ class Go118Test extends Base
'cp -Rf tests/sdks/go/* tests/tmp/go/src/github.com/repoowner/sdk-for-go/'
];
protected string $command =
- 'docker run --rm -v $(pwd):/app -w /app golang:1.18 sh -c "cd tests/languages/go/ && ./test.sh"';
+ 'docker run --network="mockapi" --rm -v $(pwd):/app -w /app golang:1.18 sh -c "cd tests/languages/go/ && ./test.sh"';
protected array $expectedOutput = [
...Base::FOO_RESPONSES,
...Base::BAR_RESPONSES,
diff --git a/tests/KotlinJava11Test.php b/tests/KotlinJava11Test.php
index 0522fc7cc..626820ed4 100644
--- a/tests/KotlinJava11Test.php
+++ b/tests/KotlinJava11Test.php
@@ -17,7 +17,7 @@ class KotlinJava11Test extends Base
'chmod +x tests/sdks/kotlin/gradlew',
];
protected string $command =
- 'docker run --rm -v $(pwd):/app -w /app/tests/sdks/kotlin openjdk:11-jdk-slim sh -c "./gradlew test -q && cat result.txt"';
+ 'docker run --network="mockapi" --rm -v $(pwd):/app -w /app/tests/sdks/kotlin openjdk:11-jdk-slim sh -c "./gradlew test -q && cat result.txt"';
protected array $expectedOutput = [
...Base::FOO_RESPONSES,
diff --git a/tests/KotlinJava17Test.php b/tests/KotlinJava17Test.php
index df1110f8b..c77c051ec 100644
--- a/tests/KotlinJava17Test.php
+++ b/tests/KotlinJava17Test.php
@@ -17,7 +17,7 @@ class KotlinJava17Test extends Base
'chmod +x tests/sdks/kotlin/gradlew',
];
protected string $command =
- 'docker run --rm -v $(pwd):/app -w /app/tests/sdks/kotlin openjdk:17-jdk-slim sh -c "./gradlew test -q && cat result.txt"';
+ 'docker run --network="mockapi" --rm -v $(pwd):/app -w /app/tests/sdks/kotlin openjdk:17-jdk-slim sh -c "./gradlew test -q && cat result.txt"';
protected array $expectedOutput = [
...Base::FOO_RESPONSES,
diff --git a/tests/KotlinJava8Test.php b/tests/KotlinJava8Test.php
index c5ded35ff..a5fc8b176 100644
--- a/tests/KotlinJava8Test.php
+++ b/tests/KotlinJava8Test.php
@@ -17,7 +17,7 @@ class KotlinJava8Test extends Base
'chmod +x tests/sdks/kotlin/gradlew',
];
protected string $command =
- 'docker run --rm -v $(pwd):/app -w /app/tests/sdks/kotlin openjdk:8-jdk-slim sh -c "./gradlew test -q && cat result.txt"';
+ 'docker run --network="mockapi" --rm -v $(pwd):/app -w /app/tests/sdks/kotlin openjdk:8-jdk-slim sh -c "./gradlew test -q && cat result.txt"';
protected array $expectedOutput = [
...Base::FOO_RESPONSES,
diff --git a/tests/Node12Test.php b/tests/Node12Test.php
index d60777322..9c0e6818e 100644
--- a/tests/Node12Test.php
+++ b/tests/Node12Test.php
@@ -16,7 +16,7 @@ class Node12Test extends Base
'docker run --rm -v $(pwd):/app -w /app/tests/sdks/node node:12-alpine npm install',
];
protected string $command =
- 'docker run --rm -v $(pwd):/app -w /app node:12-alpine node tests/languages/node/test.js';
+ 'docker run --network="mockapi" --rm -v $(pwd):/app -w /app node:12-alpine node tests/languages/node/test.js';
protected array $expectedOutput = [
...Base::FOO_RESPONSES,
diff --git a/tests/Node14Test.php b/tests/Node14Test.php
index fdacb50af..4e78c97f6 100644
--- a/tests/Node14Test.php
+++ b/tests/Node14Test.php
@@ -15,7 +15,7 @@ class Node14Test extends Base
'docker run --rm -v $(pwd):/app -w /app/tests/sdks/node node:14-alpine npm install',
];
protected string $command =
- 'docker run --rm -v $(pwd):/app -w /app node:14-alpine node tests/languages/node/test.js';
+ 'docker run --network="mockapi" --rm -v $(pwd):/app -w /app node:14-alpine node tests/languages/node/test.js';
protected array $expectedOutput = [
...Base::FOO_RESPONSES,
diff --git a/tests/Node16Test.php b/tests/Node16Test.php
index c05cd7249..af9d23d16 100644
--- a/tests/Node16Test.php
+++ b/tests/Node16Test.php
@@ -15,7 +15,7 @@ class Node16Test extends Base
'docker run --rm -v $(pwd):/app -w /app/tests/sdks/node node:16-alpine npm install',
];
protected string $command =
- 'docker run --rm -v $(pwd):/app -w /app node:16-alpine node tests/languages/node/test.js';
+ 'docker run --network="mockapi" --rm -v $(pwd):/app -w /app node:16-alpine node tests/languages/node/test.js';
protected array $expectedOutput = [
...Base::FOO_RESPONSES,
diff --git a/tests/PHP74Test.php b/tests/PHP74Test.php
index bbd296b42..1c93a1daf 100644
--- a/tests/PHP74Test.php
+++ b/tests/PHP74Test.php
@@ -13,7 +13,7 @@ class PHP74Test extends Base
protected string $class = 'Appwrite\SDK\Language\PHP';
protected array $build = [];
protected string $command =
- 'docker run --rm -v $(pwd):/app -w /app php:7.4-cli-alpine php tests/languages/php/test.php';
+ 'docker run --network="mockapi" --rm -v $(pwd):/app -w /app php:7.4-cli-alpine php tests/languages/php/test.php';
protected array $expectedOutput = [
...Base::FOO_RESPONSES,
diff --git a/tests/PHP80Test.php b/tests/PHP80Test.php
index 32a0a1c76..a141deaba 100644
--- a/tests/PHP80Test.php
+++ b/tests/PHP80Test.php
@@ -13,7 +13,7 @@ class PHP80Test extends Base
protected string $class = 'Appwrite\SDK\Language\PHP';
protected array $build = [];
protected string $command =
- 'docker run --rm -v $(pwd):/app -w /app php:8.0-cli-alpine php tests/languages/php/test.php';
+ 'docker run --network="mockapi" --rm -v $(pwd):/app -w /app php:8.0-cli-alpine php tests/languages/php/test.php';
protected array $expectedOutput = [
...Base::FOO_RESPONSES,
diff --git a/tests/Python310Test.php b/tests/Python310Test.php
index a8c5f2cef..6b6b2c5a2 100644
--- a/tests/Python310Test.php
+++ b/tests/Python310Test.php
@@ -14,10 +14,10 @@ class Python310Test extends Base
protected array $build = [
'cp tests/languages/python/tests.py tests/sdks/python/test.py',
'echo "" > tests/sdks/python/__init__.py',
- 'docker run --rm -v $(pwd):/app -w /app --env PIP_TARGET=tests/sdks/python/vendor python:3.10 pip install -r tests/sdks/python/requirements.txt --upgrade',
+ 'docker run --rm -v $(pwd):/app -w /app --env PIP_TARGET=tests/sdks/python/vendor python:3.10-alpine pip install -r tests/sdks/python/requirements.txt --upgrade',
];
protected string $command =
- 'docker run --rm -v $(pwd):/app -w /app --env PIP_TARGET=tests/sdks/python/vendor --env PYTHONPATH=tests/sdks/python/vendor python:3.10-alpine python tests/sdks/python/test.py';
+ 'docker run --network="mockapi" --rm -v $(pwd):/app -w /app --env PIP_TARGET=tests/sdks/python/vendor --env PYTHONPATH=tests/sdks/python/vendor python:3.10-alpine python tests/sdks/python/test.py';
protected array $expectedOutput = [
...Base::FOO_RESPONSES,
diff --git a/tests/Python38Test.php b/tests/Python38Test.php
index 0fc836224..1cfe30cc3 100644
--- a/tests/Python38Test.php
+++ b/tests/Python38Test.php
@@ -14,10 +14,10 @@ class Python38Test extends Base
protected array $build = [
'cp tests/languages/python/tests.py tests/sdks/python/test.py',
'echo "" > tests/sdks/python/__init__.py',
- 'docker run --rm -v $(pwd):/app -w /app --env PIP_TARGET=tests/sdks/python/vendor python:3.8 pip install -r tests/sdks/python/requirements.txt --upgrade',
+ 'docker run --rm -v $(pwd):/app -w /app --env PIP_TARGET=tests/sdks/python/vendor python:3.8-alpine pip install -r tests/sdks/python/requirements.txt --upgrade',
];
protected string $command =
- 'docker run --rm -v $(pwd):/app -w /app --env PIP_TARGET=tests/sdks/python/vendor --env PYTHONPATH=tests/sdks/python/vendor python:3.8-alpine python tests/sdks/python/test.py';
+ 'docker run --network="mockapi" --rm -v $(pwd):/app -w /app --env PIP_TARGET=tests/sdks/python/vendor --env PYTHONPATH=tests/sdks/python/vendor python:3.8-alpine python tests/sdks/python/test.py';
protected array $expectedOutput = [
...Base::FOO_RESPONSES,
diff --git a/tests/Python39Test.php b/tests/Python39Test.php
index 5dbfe4fcf..008cca5df 100644
--- a/tests/Python39Test.php
+++ b/tests/Python39Test.php
@@ -14,10 +14,10 @@ class Python39Test extends Base
protected array $build = [
'cp tests/languages/python/tests.py tests/sdks/python/test.py',
'echo "" > tests/sdks/python/__init__.py',
- 'docker run --rm -v $(pwd):/app -w /app --env PIP_TARGET=tests/sdks/python/vendor python:3.9 pip install -r tests/sdks/python/requirements.txt --upgrade',
+ 'docker run --rm -v $(pwd):/app -w /app --env PIP_TARGET=tests/sdks/python/vendor python:3.9-alpine pip install -r tests/sdks/python/requirements.txt --upgrade',
];
protected string $command =
- 'docker run --rm -v $(pwd):/app -w /app --env PIP_TARGET=tests/sdks/python/vendor --env PYTHONPATH=tests/sdks/python/vendor python:3.9-alpine python tests/sdks/python/test.py';
+ 'docker run --network="mockapi" --rm -v $(pwd):/app -w /app --env PIP_TARGET=tests/sdks/python/vendor --env PYTHONPATH=tests/sdks/python/vendor python:3.9-alpine python tests/sdks/python/test.py';
protected array $expectedOutput = [
...Base::FOO_RESPONSES,
diff --git a/tests/Ruby27Test.php b/tests/Ruby27Test.php
index 2d5af6d0d..d17b42966 100644
--- a/tests/Ruby27Test.php
+++ b/tests/Ruby27Test.php
@@ -15,7 +15,7 @@ class Ruby27Test extends Base
'docker run --rm -v $(pwd):/app -w /app/tests/sdks/ruby --env GEM_HOME=/app/vendor ruby:2.7-alpine sh -c "apk add git build-base && bundle install"',
];
protected string $command =
- 'docker run --rm -v $(pwd):/app -w /app --env GEM_HOME=vendor ruby:2.7-alpine ruby tests/languages/ruby/tests.rb';
+ 'docker run --network="mockapi" --rm -v $(pwd):/app -w /app --env GEM_HOME=vendor ruby:2.7-alpine ruby tests/languages/ruby/tests.rb';
protected array $expectedOutput = [
...Base::FOO_RESPONSES,
diff --git a/tests/Ruby30Test.php b/tests/Ruby30Test.php
index 1d2d4c102..8a135c82c 100644
--- a/tests/Ruby30Test.php
+++ b/tests/Ruby30Test.php
@@ -15,7 +15,7 @@ class Ruby30Test extends Base
'docker run --rm -v $(pwd):/app -w /app/tests/sdks/ruby --env GEM_HOME=/app/vendor ruby:3.0-alpine sh -c "apk add git build-base && bundle install"',
];
protected string $command =
- 'docker run --rm -v $(pwd):/app -w /app --env GEM_HOME=vendor ruby:3.0-alpine ruby tests/languages/ruby/tests.rb';
+ 'docker run --network="mockapi" --rm -v $(pwd):/app -w /app --env GEM_HOME=vendor ruby:3.0-alpine ruby tests/languages/ruby/tests.rb';
protected array $expectedOutput = [
...Base::FOO_RESPONSES,
diff --git a/tests/Ruby31Test.php b/tests/Ruby31Test.php
index 97039da86..0cf7a122b 100644
--- a/tests/Ruby31Test.php
+++ b/tests/Ruby31Test.php
@@ -15,7 +15,7 @@ class Ruby31Test extends Base
'docker run --rm -v $(pwd):/app -w /app/tests/sdks/ruby --env GEM_HOME=/app/vendor ruby:3.1-alpine sh -c "apk add git build-base && bundle install"',
];
protected string $command =
- 'docker run --rm -v $(pwd):/app -w /app --env GEM_HOME=vendor ruby:3.1-alpine ruby tests/languages/ruby/tests.rb';
+ 'docker run --network="mockapi" --rm -v $(pwd):/app -w /app --env GEM_HOME=vendor ruby:3.1-alpine ruby tests/languages/ruby/tests.rb';
protected array $expectedOutput = [
...Base::FOO_RESPONSES,
diff --git a/tests/Swift55Test.php b/tests/Swift55Test.php
index 568de657a..c8fd09fcc 100644
--- a/tests/Swift55Test.php
+++ b/tests/Swift55Test.php
@@ -16,7 +16,7 @@ class Swift55Test extends Base
'cp tests/languages/swift/Tests.swift tests/sdks/swift/Tests/AppwriteTests/Tests.swift',
];
protected string $command =
- 'docker run --rm -v $(pwd):/app -w /app/tests/sdks/swift swiftarm/swift:5.5.2-focal-multi-arch swift test';
+ 'docker run --network="mockapi" --rm -v $(pwd):/app -w /app/tests/sdks/swift swiftarm/swift:5.5.2-focal-multi-arch swift test';
protected array $expectedOutput = [
...Base::FOO_RESPONSES,
diff --git a/tests/WebChromiumTest.php b/tests/WebChromiumTest.php
index b1d463e23..967957763 100644
--- a/tests/WebChromiumTest.php
+++ b/tests/WebChromiumTest.php
@@ -19,7 +19,7 @@ class WebChromiumTest extends Base
'docker run --rm -v $(pwd):/app -w /app/tests/sdks/web mcr.microsoft.com/playwright:v1.15.0-focal npm run build',
];
protected string $command =
- 'docker run --rm -v $(pwd):/app -e BROWSER=chromium -w /app/tests/sdks/web mcr.microsoft.com/playwright:v1.15.0-focal node tests.js';
+ 'docker run --network="mockapi" --rm -v $(pwd):/app -e BROWSER=chromium -w /app/tests/sdks/web mcr.microsoft.com/playwright:v1.15.0-focal node tests.js';
protected array $expectedOutput = [
...Base::FOO_RESPONSES,
diff --git a/tests/WebNodeTest.php b/tests/WebNodeTest.php
index b6154aa1a..9aa26a790 100644
--- a/tests/WebNodeTest.php
+++ b/tests/WebNodeTest.php
@@ -19,7 +19,7 @@ class WebNodeTest extends Base
'docker run --rm -v $(pwd):/app -w /app/tests/sdks/web mcr.microsoft.com/playwright:v1.15.0-focal npm run build',
];
protected string $command =
- 'docker run --rm -v $(pwd):/app -w /app/tests/sdks/web node:18-alpine node node.js';
+ 'docker run --network="mockapi" --rm -v $(pwd):/app -w /app/tests/sdks/web node:18-alpine node node.js';
protected array $expectedOutput = [
...Base::FOO_RESPONSES,
diff --git a/tests/languages/android/Tests.kt b/tests/languages/android/Tests.kt
index 0df42c522..b8b608f5a 100644
--- a/tests/languages/android/Tests.kt
+++ b/tests/languages/android/Tests.kt
@@ -152,11 +152,11 @@ class ServiceTest {
delay(5000)
writeToFile(realtimeResponse)
- mock = general.setCookie()
- writeToFile(mock.result)
+ // mock = general.setCookie()
+ // writeToFile(mock.result)
- mock = general.getCookie()
- writeToFile(mock.result)
+ // mock = general.getCookie()
+ // writeToFile(mock.result)
general.empty()
@@ -192,6 +192,7 @@ class ServiceTest {
writeToFile(Permission.create(Role.member("memberId")))
writeToFile(Permission.update(Role.users("verified")))
writeToFile(Permission.update(Role.user(ID.custom("userid"), "unverified")))
+ writeToFile(Permission.create(Role.label("admin")))
// ID helper tests
writeToFile(ID.unique())
diff --git a/tests/languages/apple/Tests.swift b/tests/languages/apple/Tests.swift
index 64fa928a7..06cd5144f 100644
--- a/tests/languages/apple/Tests.swift
+++ b/tests/languages/apple/Tests.swift
@@ -175,6 +175,7 @@ class Tests: XCTestCase {
print(Permission.create(Role.member("memberId")))
print(Permission.update(Role.users("verified")))
print(Permission.update(Role.user(ID.custom("userid"), "unverified")))
+ print(Permission.create(Role.label("admin")))
// ID helper tests
print(ID.unique())
diff --git a/tests/languages/cli/test.js b/tests/languages/cli/test.js
index 6c814738e..782fe357f 100644
--- a/tests/languages/cli/test.js
+++ b/tests/languages/cli/test.js
@@ -1,6 +1,6 @@
const { exec, execSync } = require('child_process');
-execSync("node index client --endpoint 'https://stage.appwrite.io/v1' --projectId console --key=35y3h5h345 --selfSigned true", { stdio: 'inherit' });
+execSync("node index client --endpoint 'https://qa.appwrite.org/v1' --projectId console --key=35y3h5h345 --selfSigned true", { stdio: 'inherit' });
var output;
console.log('\nTest Started');
diff --git a/tests/languages/dart/tests.dart b/tests/languages/dart/tests.dart
index a77117dfc..94c474ab2 100644
--- a/tests/languages/dart/tests.dart
+++ b/tests/languages/dart/tests.dart
@@ -120,6 +120,7 @@ void main() async {
print(Permission.create(Role.member('memberId')));
print(Permission.update(Role.users('verified')));
print(Permission.update(Role.user(ID.custom('userid'), 'unverified')));
+ print(Permission.create(Role.label('admin')));
// ID helper tests
print(ID.unique());
diff --git a/tests/languages/deno/tests.ts b/tests/languages/deno/tests.ts
index ae9cfe37e..f0bb47138 100644
--- a/tests/languages/deno/tests.ts
+++ b/tests/languages/deno/tests.ts
@@ -129,6 +129,7 @@ async function start() {
console.log(Permission.create(Role.member('memberId')));
console.log(Permission.update(Role.users('verified')));
console.log(Permission.update(Role.user(ID.custom('userid'), 'unverified')));
+ console.log(Permission.create(Role.label('admin')));
// ID helper tests
console.log(ID.unique());
diff --git a/tests/languages/dotnet/Tests.cs b/tests/languages/dotnet/Tests.cs
index cb6b765d6..c01946a74 100644
--- a/tests/languages/dotnet/Tests.cs
+++ b/tests/languages/dotnet/Tests.cs
@@ -136,8 +136,9 @@ public async Task Test1()
TestContext.WriteLine(Permission.Delete(Role.Team("teamId", "owner")));
TestContext.WriteLine(Permission.Delete(Role.Team("teamId")));
TestContext.WriteLine(Permission.Create(Role.Member("memberId")));
- TestContext.WriteLine(Permission.Update(Role.Users("verified")));;
- TestContext.WriteLine(Permission.Update(Role.User(ID.Custom("userid"), "unverified")));;
+ TestContext.WriteLine(Permission.Update(Role.Users("verified")));
+ TestContext.WriteLine(Permission.Update(Role.User(ID.Custom("userid"), "unverified")));
+ TestContext.WriteLine(Permission.Create(Role.Label("admin")));
// ID helper tests
TestContext.WriteLine(ID.Unique());
diff --git a/tests/languages/flutter/tests.dart b/tests/languages/flutter/tests.dart
index 8b13681e4..b6fb89e3b 100644
--- a/tests/languages/flutter/tests.dart
+++ b/tests/languages/flutter/tests.dart
@@ -153,6 +153,7 @@ void main() async {
print(Permission.create(Role.member('memberId')));
print(Permission.update(Role.users('verified')));
print(Permission.update(Role.user(ID.custom('userid'), 'unverified')));
+ print(Permission.create(Role.label('admin')));
// ID helper tests
print(ID.unique());
diff --git a/tests/languages/kotlin/Tests.kt b/tests/languages/kotlin/Tests.kt
index 0edf3e896..33b4ae06a 100644
--- a/tests/languages/kotlin/Tests.kt
+++ b/tests/languages/kotlin/Tests.kt
@@ -152,6 +152,7 @@ class ServiceTest {
writeToFile(Permission.create(Role.member("memberId")))
writeToFile(Permission.update(Role.users("verified")));
writeToFile(Permission.update(Role.user(ID.custom("userid"), "unverified")));
+ writeToFile(Permission.create(Role.label("admin")));
// ID helper tests
writeToFile(ID.unique())
diff --git a/tests/languages/node/test.js b/tests/languages/node/test.js
index 03da67da1..259e7c0ea 100644
--- a/tests/languages/node/test.js
+++ b/tests/languages/node/test.js
@@ -117,6 +117,7 @@ async function start() {
console.log(Permission.create(Role.member('memberId')));
console.log(Permission.update(Role.users('verified')));
console.log(Permission.update(Role.user(ID.custom('userid'), 'unverified')));
+ console.log(Permission.create(Role.label('admin')));
// ID helper tests
console.log(ID.unique());
diff --git a/tests/languages/php/test.php b/tests/languages/php/test.php
index 2bc98da87..4f74f4e60 100644
--- a/tests/languages/php/test.php
+++ b/tests/languages/php/test.php
@@ -135,6 +135,7 @@
echo Permission::create(Role::member('memberId')) . "\n";
echo Permission::update(Role::users('verified')) . "\n";
echo Permission::update(Role::user(ID::custom('userid'), 'unverified')) . "\n";
+echo Permission::create(Role::label('admin')) . "\n";
// ID helper tests
echo ID::unique() . "\n";
diff --git a/tests/languages/python/tests.py b/tests/languages/python/tests.py
index 9c262b29c..99f70c301 100644
--- a/tests/languages/python/tests.py
+++ b/tests/languages/python/tests.py
@@ -124,6 +124,7 @@
print(Permission.create(Role.member('memberId')))
print(Permission.update(Role.users('verified')))
print(Permission.update(Role.user(ID.custom('userid'), 'unverified')))
+print(Permission.create(Role.label('admin')))
# ID helper tests
print(ID.unique())
diff --git a/tests/languages/ruby/tests.rb b/tests/languages/ruby/tests.rb
index dc14cafe4..4a76966b0 100644
--- a/tests/languages/ruby/tests.rb
+++ b/tests/languages/ruby/tests.rb
@@ -3,6 +3,7 @@
include Appwrite
client = Client.new
+client.set_self_signed(true)
client.add_header('Origin', 'http://localhost')
foo = Foo.new(client)
@@ -133,6 +134,7 @@
puts Permission.create(Role.member('memberId'))
puts Permission.update(Role.users('verified'))
puts Permission.update(Role.user(ID.custom('userid'), 'unverified'))
+puts Permission.create(Role.label('admin'))
# ID helper tests
puts ID.unique()
diff --git a/tests/languages/swift/Tests.swift b/tests/languages/swift/Tests.swift
index ab8d3847b..948de3601 100644
--- a/tests/languages/swift/Tests.swift
+++ b/tests/languages/swift/Tests.swift
@@ -156,6 +156,7 @@ class Tests: XCTestCase {
print(Permission.create(Role.member("memberId")))
print(Permission.update(Role.users("verified")))
print(Permission.update(Role.user(ID.custom("userid"), "unverified")))
+ print(Permission.create(Role.label("admin")))
// ID helper tests
print(ID.unique())
diff --git a/tests/languages/web/index.html b/tests/languages/web/index.html
index b7396a113..17387180f 100644
--- a/tests/languages/web/index.html
+++ b/tests/languages/web/index.html
@@ -145,6 +145,7 @@
console.log(Permission.create(Role.member('memberId')));
console.log(Permission.update(Role.users('verified')));
console.log(Permission.update(Role.user(ID.custom('userid'), 'unverified')));
+ console.log(Permission.create(Role.label('admin')));
// ID helper tests
diff --git a/tests/languages/web/node.js b/tests/languages/web/node.js
index 9a95c0a70..0ec235512 100644
--- a/tests/languages/web/node.js
+++ b/tests/languages/web/node.js
@@ -102,6 +102,7 @@ async function start() {
console.log(Permission.create(Role.member('memberId')));
console.log(Permission.update(Role.users('verified')));
console.log(Permission.update(Role.user(ID.custom('userid'), 'unverified')));
+ console.log(Permission.create(Role.label('admin')));
// ID helper tests
diff --git a/tests/resources/spec.json b/tests/resources/spec.json
index fae575b37..772602681 100644
--- a/tests/resources/spec.json
+++ b/tests/resources/spec.json
@@ -15,9 +15,9 @@
"url": "https://raw.githubusercontent.com/appwrite/appwrite/master/LICENSE"
}
},
- "host": "stage.appwrite.io",
+ "host": "mockapi",
"basePath": "/v1",
- "schemes": ["https"],
+ "schemes": ["http"],
"consumes": ["application/json", "multipart/form-data"],
"produces": ["application/json"],
"securityDefinitions": {