Merge branch '1.8.x' into feat-operators

This commit is contained in:
Jake Barnby 2025-11-13 11:29:05 +00:00 committed by GitHub
commit 298e6f9260
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
53 changed files with 1410 additions and 138 deletions

View file

@ -12,7 +12,7 @@ RUN composer install --ignore-platform-reqs --optimize-autoloader \
--no-plugins --no-scripts --prefer-dist \
`if [ "$TESTING" != "true" ]; then echo "--no-dev"; fi`
FROM appwrite/base:0.10.4 AS final
FROM appwrite/base:0.10.5 AS final
LABEL maintainer="team@appwrite.io"
@ -28,8 +28,6 @@ RUN \
apk add boost boost-dev; \
fi
RUN apk add libwebp
WORKDIR /usr/src/code
COPY --from=composer /usr/local/src/vendor /usr/src/code/vendor

View file

@ -273,7 +273,7 @@ return [
'key' => 'flutter',
'name' => 'Flutter',
'screenshotSleep' => 5000,
'buildRuntime' => 'flutter-3.29',
'buildRuntime' => 'flutter-3.35',
'runtimes' => getVersions($templateRuntimes['FLUTTER']['versions'], 'flutter'),
'adapters' => [
'static' => [
@ -282,6 +282,7 @@ return [
'installCommand' => 'flutter pub get',
'outputDirectory' => './build/web',
'startCommand' => 'bash helpers/server.sh',
'fallbackFile' => 'index.html'
],
],
],

View file

@ -60,7 +60,7 @@ return [
[
'key' => 'flutter',
'name' => 'Flutter',
'version' => '20.3.0',
'version' => '20.3.1',
'url' => 'https://github.com/appwrite/sdk-for-flutter',
'package' => 'https://pub.dev/packages/appwrite',
'enabled' => true,
@ -376,7 +376,7 @@ return [
[
'key' => 'dart',
'name' => 'Dart',
'version' => '19.3.0',
'version' => '19.4.0',
'url' => 'https://github.com/appwrite/sdk-for-dart',
'package' => 'https://pub.dev/packages/dart_appwrite',
'enabled' => true,

View file

@ -13105,6 +13105,7 @@
"dart-3.3",
"dart-3.5",
"dart-3.8",
"dart-3.9",
"dotnet-6.0",
"dotnet-7.0",
"dotnet-8.0",
@ -13131,7 +13132,8 @@
"flutter-3.24",
"flutter-3.27",
"flutter-3.29",
"flutter-3.32"
"flutter-3.32",
"flutter-3.35"
],
"x-enum-name": null,
"x-enum-keys": []
@ -13746,6 +13748,7 @@
"dart-3.3",
"dart-3.5",
"dart-3.8",
"dart-3.9",
"dotnet-6.0",
"dotnet-7.0",
"dotnet-8.0",
@ -13772,7 +13775,8 @@
"flutter-3.24",
"flutter-3.27",
"flutter-3.29",
"flutter-3.32"
"flutter-3.32",
"flutter-3.35"
],
"x-enum-name": null,
"x-enum-keys": []
@ -30954,6 +30958,7 @@
"dart-3.3",
"dart-3.5",
"dart-3.8",
"dart-3.9",
"dotnet-6.0",
"dotnet-7.0",
"dotnet-8.0",
@ -30980,7 +30985,8 @@
"flutter-3.24",
"flutter-3.27",
"flutter-3.29",
"flutter-3.32"
"flutter-3.32",
"flutter-3.35"
],
"x-enum-name": null,
"x-enum-keys": []
@ -31601,6 +31607,7 @@
"dart-3.3",
"dart-3.5",
"dart-3.8",
"dart-3.9",
"dotnet-6.0",
"dotnet-7.0",
"dotnet-8.0",
@ -31627,7 +31634,8 @@
"flutter-3.24",
"flutter-3.27",
"flutter-3.29",
"flutter-3.32"
"flutter-3.32",
"flutter-3.35"
],
"x-enum-name": null,
"x-enum-keys": []

View file

@ -12127,6 +12127,7 @@
"dart-3.3",
"dart-3.5",
"dart-3.8",
"dart-3.9",
"dotnet-6.0",
"dotnet-7.0",
"dotnet-8.0",
@ -12153,7 +12154,8 @@
"flutter-3.24",
"flutter-3.27",
"flutter-3.29",
"flutter-3.32"
"flutter-3.32",
"flutter-3.35"
],
"x-enum-name": null,
"x-enum-keys": []
@ -12529,6 +12531,7 @@
"dart-3.3",
"dart-3.5",
"dart-3.8",
"dart-3.9",
"dotnet-6.0",
"dotnet-7.0",
"dotnet-8.0",
@ -12555,7 +12558,8 @@
"flutter-3.24",
"flutter-3.27",
"flutter-3.29",
"flutter-3.32"
"flutter-3.32",
"flutter-3.35"
],
"x-enum-name": null,
"x-enum-keys": []
@ -21703,6 +21707,7 @@
"dart-3.3",
"dart-3.5",
"dart-3.8",
"dart-3.9",
"dotnet-6.0",
"dotnet-7.0",
"dotnet-8.0",
@ -21729,7 +21734,8 @@
"flutter-3.24",
"flutter-3.27",
"flutter-3.29",
"flutter-3.32"
"flutter-3.32",
"flutter-3.35"
],
"x-enum-name": null,
"x-enum-keys": []
@ -22122,6 +22128,7 @@
"dart-3.3",
"dart-3.5",
"dart-3.8",
"dart-3.9",
"dotnet-6.0",
"dotnet-7.0",
"dotnet-8.0",
@ -22148,7 +22155,8 @@
"flutter-3.24",
"flutter-3.27",
"flutter-3.29",
"flutter-3.32"
"flutter-3.32",
"flutter-3.35"
],
"x-enum-name": null,
"x-enum-keys": []

View file

@ -13105,6 +13105,7 @@
"dart-3.3",
"dart-3.5",
"dart-3.8",
"dart-3.9",
"dotnet-6.0",
"dotnet-7.0",
"dotnet-8.0",
@ -13131,7 +13132,8 @@
"flutter-3.24",
"flutter-3.27",
"flutter-3.29",
"flutter-3.32"
"flutter-3.32",
"flutter-3.35"
],
"x-enum-name": null,
"x-enum-keys": []
@ -13746,6 +13748,7 @@
"dart-3.3",
"dart-3.5",
"dart-3.8",
"dart-3.9",
"dotnet-6.0",
"dotnet-7.0",
"dotnet-8.0",
@ -13772,7 +13775,8 @@
"flutter-3.24",
"flutter-3.27",
"flutter-3.29",
"flutter-3.32"
"flutter-3.32",
"flutter-3.35"
],
"x-enum-name": null,
"x-enum-keys": []
@ -14359,10 +14363,22 @@
"description": "Path to function code in the template repo.",
"x-example": "<ROOT_DIRECTORY>"
},
"version": {
"type": {
"type": "string",
"description": "Version (tag) for the repo linked to the function template.",
"x-example": "<VERSION>"
"description": "Type for the reference provided. Can be commit, branch, or tag",
"x-example": "commit",
"enum": [
"commit",
"branch",
"tag"
],
"x-enum-name": null,
"x-enum-keys": []
},
"reference": {
"type": "string",
"description": "Reference value, can be a commit hash, branch name, or release tag",
"x-example": "<REFERENCE>"
},
"activate": {
"type": "boolean",
@ -14374,7 +14390,8 @@
"repository",
"owner",
"rootDirectory",
"version"
"type",
"reference"
]
}
}
@ -30954,6 +30971,7 @@
"dart-3.3",
"dart-3.5",
"dart-3.8",
"dart-3.9",
"dotnet-6.0",
"dotnet-7.0",
"dotnet-8.0",
@ -30980,7 +30998,8 @@
"flutter-3.24",
"flutter-3.27",
"flutter-3.29",
"flutter-3.32"
"flutter-3.32",
"flutter-3.35"
],
"x-enum-name": null,
"x-enum-keys": []
@ -31601,6 +31620,7 @@
"dart-3.3",
"dart-3.5",
"dart-3.8",
"dart-3.9",
"dotnet-6.0",
"dotnet-7.0",
"dotnet-8.0",
@ -31627,7 +31647,8 @@
"flutter-3.24",
"flutter-3.27",
"flutter-3.29",
"flutter-3.32"
"flutter-3.32",
"flutter-3.35"
],
"x-enum-name": null,
"x-enum-keys": []
@ -32176,10 +32197,22 @@
"description": "Path to site code in the template repo.",
"x-example": "<ROOT_DIRECTORY>"
},
"version": {
"type": {
"type": "string",
"description": "Version (tag) for the repo linked to the site template.",
"x-example": "<VERSION>"
"description": "Type for the reference provided. Can be commit, branch, or tag",
"x-example": "branch",
"enum": [
"branch",
"commit",
"tag"
],
"x-enum-name": null,
"x-enum-keys": []
},
"reference": {
"type": "string",
"description": "Reference value, can be a commit hash, branch name, or release tag",
"x-example": "<REFERENCE>"
},
"activate": {
"type": "boolean",
@ -32191,7 +32224,8 @@
"repository",
"owner",
"rootDirectory",
"version"
"type",
"reference"
]
}
}

View file

@ -12127,6 +12127,7 @@
"dart-3.3",
"dart-3.5",
"dart-3.8",
"dart-3.9",
"dotnet-6.0",
"dotnet-7.0",
"dotnet-8.0",
@ -12153,7 +12154,8 @@
"flutter-3.24",
"flutter-3.27",
"flutter-3.29",
"flutter-3.32"
"flutter-3.32",
"flutter-3.35"
],
"x-enum-name": null,
"x-enum-keys": []
@ -12529,6 +12531,7 @@
"dart-3.3",
"dart-3.5",
"dart-3.8",
"dart-3.9",
"dotnet-6.0",
"dotnet-7.0",
"dotnet-8.0",
@ -12555,7 +12558,8 @@
"flutter-3.24",
"flutter-3.27",
"flutter-3.29",
"flutter-3.32"
"flutter-3.32",
"flutter-3.35"
],
"x-enum-name": null,
"x-enum-keys": []
@ -13148,10 +13152,22 @@
"description": "Path to function code in the template repo.",
"x-example": "<ROOT_DIRECTORY>"
},
"version": {
"type": {
"type": "string",
"description": "Version (tag) for the repo linked to the function template.",
"x-example": "<VERSION>"
"description": "Type for the reference provided. Can be commit, branch, or tag",
"x-example": "commit",
"enum": [
"commit",
"branch",
"tag"
],
"x-enum-name": null,
"x-enum-keys": []
},
"reference": {
"type": "string",
"description": "Reference value, can be a commit hash, branch name, or release tag",
"x-example": "<REFERENCE>"
},
"activate": {
"type": "boolean",
@ -13163,7 +13179,8 @@
"repository",
"owner",
"rootDirectory",
"version"
"type",
"reference"
]
}
}
@ -21703,6 +21720,7 @@
"dart-3.3",
"dart-3.5",
"dart-3.8",
"dart-3.9",
"dotnet-6.0",
"dotnet-7.0",
"dotnet-8.0",
@ -21729,7 +21747,8 @@
"flutter-3.24",
"flutter-3.27",
"flutter-3.29",
"flutter-3.32"
"flutter-3.32",
"flutter-3.35"
],
"x-enum-name": null,
"x-enum-keys": []
@ -22122,6 +22141,7 @@
"dart-3.3",
"dart-3.5",
"dart-3.8",
"dart-3.9",
"dotnet-6.0",
"dotnet-7.0",
"dotnet-8.0",
@ -22148,7 +22168,8 @@
"flutter-3.24",
"flutter-3.27",
"flutter-3.29",
"flutter-3.32"
"flutter-3.32",
"flutter-3.35"
],
"x-enum-name": null,
"x-enum-keys": []
@ -22703,10 +22724,22 @@
"description": "Path to site code in the template repo.",
"x-example": "<ROOT_DIRECTORY>"
},
"version": {
"type": {
"type": "string",
"description": "Version (tag) for the repo linked to the site template.",
"x-example": "<VERSION>"
"description": "Type for the reference provided. Can be commit, branch, or tag",
"x-example": "branch",
"enum": [
"branch",
"commit",
"tag"
],
"x-enum-name": null,
"x-enum-keys": []
},
"reference": {
"type": "string",
"description": "Reference value, can be a commit hash, branch name, or release tag",
"x-example": "<REFERENCE>"
},
"activate": {
"type": "boolean",
@ -22718,7 +22751,8 @@
"repository",
"owner",
"rootDirectory",
"version"
"type",
"reference"
]
}
}

View file

@ -13041,6 +13041,7 @@
"dart-3.3",
"dart-3.5",
"dart-3.8",
"dart-3.9",
"dotnet-6.0",
"dotnet-7.0",
"dotnet-8.0",
@ -13067,7 +13068,8 @@
"flutter-3.24",
"flutter-3.27",
"flutter-3.29",
"flutter-3.32"
"flutter-3.32",
"flutter-3.35"
],
"x-enum-name": null,
"x-enum-keys": []
@ -13683,6 +13685,7 @@
"dart-3.3",
"dart-3.5",
"dart-3.8",
"dart-3.9",
"dotnet-6.0",
"dotnet-7.0",
"dotnet-8.0",
@ -13709,7 +13712,8 @@
"flutter-3.24",
"flutter-3.27",
"flutter-3.29",
"flutter-3.32"
"flutter-3.32",
"flutter-3.35"
],
"x-enum-name": null,
"x-enum-keys": []
@ -31058,6 +31062,7 @@
"dart-3.3",
"dart-3.5",
"dart-3.8",
"dart-3.9",
"dotnet-6.0",
"dotnet-7.0",
"dotnet-8.0",
@ -31084,7 +31089,8 @@
"flutter-3.24",
"flutter-3.27",
"flutter-3.29",
"flutter-3.32"
"flutter-3.32",
"flutter-3.35"
],
"x-enum-name": null,
"x-enum-keys": []
@ -31708,6 +31714,7 @@
"dart-3.3",
"dart-3.5",
"dart-3.8",
"dart-3.9",
"dotnet-6.0",
"dotnet-7.0",
"dotnet-8.0",
@ -31734,7 +31741,8 @@
"flutter-3.24",
"flutter-3.27",
"flutter-3.29",
"flutter-3.32"
"flutter-3.32",
"flutter-3.35"
],
"x-enum-name": null,
"x-enum-keys": []

View file

@ -12078,6 +12078,7 @@
"dart-3.3",
"dart-3.5",
"dart-3.8",
"dart-3.9",
"dotnet-6.0",
"dotnet-7.0",
"dotnet-8.0",
@ -12104,7 +12105,8 @@
"flutter-3.24",
"flutter-3.27",
"flutter-3.29",
"flutter-3.32"
"flutter-3.32",
"flutter-3.35"
],
"x-enum-name": null,
"x-enum-keys": []
@ -12493,6 +12495,7 @@
"dart-3.3",
"dart-3.5",
"dart-3.8",
"dart-3.9",
"dotnet-6.0",
"dotnet-7.0",
"dotnet-8.0",
@ -12519,7 +12522,8 @@
"flutter-3.24",
"flutter-3.27",
"flutter-3.29",
"flutter-3.32"
"flutter-3.32",
"flutter-3.35"
],
"x-enum-name": null,
"x-enum-keys": []
@ -21845,6 +21849,7 @@
"dart-3.3",
"dart-3.5",
"dart-3.8",
"dart-3.9",
"dotnet-6.0",
"dotnet-7.0",
"dotnet-8.0",
@ -21871,7 +21876,8 @@
"flutter-3.24",
"flutter-3.27",
"flutter-3.29",
"flutter-3.32"
"flutter-3.32",
"flutter-3.35"
],
"x-enum-name": null,
"x-enum-keys": []
@ -22277,6 +22283,7 @@
"dart-3.3",
"dart-3.5",
"dart-3.8",
"dart-3.9",
"dotnet-6.0",
"dotnet-7.0",
"dotnet-8.0",
@ -22303,7 +22310,8 @@
"flutter-3.24",
"flutter-3.27",
"flutter-3.29",
"flutter-3.32"
"flutter-3.32",
"flutter-3.35"
],
"x-enum-name": null,
"x-enum-keys": []

View file

@ -13041,6 +13041,7 @@
"dart-3.3",
"dart-3.5",
"dart-3.8",
"dart-3.9",
"dotnet-6.0",
"dotnet-7.0",
"dotnet-8.0",
@ -13067,7 +13068,8 @@
"flutter-3.24",
"flutter-3.27",
"flutter-3.29",
"flutter-3.32"
"flutter-3.32",
"flutter-3.35"
],
"x-enum-name": null,
"x-enum-keys": []
@ -13683,6 +13685,7 @@
"dart-3.3",
"dart-3.5",
"dart-3.8",
"dart-3.9",
"dotnet-6.0",
"dotnet-7.0",
"dotnet-8.0",
@ -13709,7 +13712,8 @@
"flutter-3.24",
"flutter-3.27",
"flutter-3.29",
"flutter-3.32"
"flutter-3.32",
"flutter-3.35"
],
"x-enum-name": null,
"x-enum-keys": []
@ -14302,11 +14306,24 @@
"default": null,
"x-example": "<ROOT_DIRECTORY>"
},
"version": {
"type": {
"type": "string",
"description": "Version (tag) for the repo linked to the function template.",
"description": "Type for the reference provided. Can be commit, branch, or tag",
"default": null,
"x-example": "<VERSION>"
"x-example": "commit",
"enum": [
"commit",
"branch",
"tag"
],
"x-enum-name": null,
"x-enum-keys": []
},
"reference": {
"type": "string",
"description": "Reference value, can be a commit hash, branch name, or release tag",
"default": null,
"x-example": "<REFERENCE>"
},
"activate": {
"type": "boolean",
@ -14319,7 +14336,8 @@
"repository",
"owner",
"rootDirectory",
"version"
"type",
"reference"
]
}
}
@ -31058,6 +31076,7 @@
"dart-3.3",
"dart-3.5",
"dart-3.8",
"dart-3.9",
"dotnet-6.0",
"dotnet-7.0",
"dotnet-8.0",
@ -31084,7 +31103,8 @@
"flutter-3.24",
"flutter-3.27",
"flutter-3.29",
"flutter-3.32"
"flutter-3.32",
"flutter-3.35"
],
"x-enum-name": null,
"x-enum-keys": []
@ -31708,6 +31728,7 @@
"dart-3.3",
"dart-3.5",
"dart-3.8",
"dart-3.9",
"dotnet-6.0",
"dotnet-7.0",
"dotnet-8.0",
@ -31734,7 +31755,8 @@
"flutter-3.24",
"flutter-3.27",
"flutter-3.29",
"flutter-3.32"
"flutter-3.32",
"flutter-3.35"
],
"x-enum-name": null,
"x-enum-keys": []
@ -32284,11 +32306,24 @@
"default": null,
"x-example": "<ROOT_DIRECTORY>"
},
"version": {
"type": {
"type": "string",
"description": "Version (tag) for the repo linked to the site template.",
"description": "Type for the reference provided. Can be commit, branch, or tag",
"default": null,
"x-example": "<VERSION>"
"x-example": "branch",
"enum": [
"branch",
"commit",
"tag"
],
"x-enum-name": null,
"x-enum-keys": []
},
"reference": {
"type": "string",
"description": "Reference value, can be a commit hash, branch name, or release tag",
"default": null,
"x-example": "<REFERENCE>"
},
"activate": {
"type": "boolean",
@ -32301,7 +32336,8 @@
"repository",
"owner",
"rootDirectory",
"version"
"type",
"reference"
]
}
}

View file

@ -12078,6 +12078,7 @@
"dart-3.3",
"dart-3.5",
"dart-3.8",
"dart-3.9",
"dotnet-6.0",
"dotnet-7.0",
"dotnet-8.0",
@ -12104,7 +12105,8 @@
"flutter-3.24",
"flutter-3.27",
"flutter-3.29",
"flutter-3.32"
"flutter-3.32",
"flutter-3.35"
],
"x-enum-name": null,
"x-enum-keys": []
@ -12493,6 +12495,7 @@
"dart-3.3",
"dart-3.5",
"dart-3.8",
"dart-3.9",
"dotnet-6.0",
"dotnet-7.0",
"dotnet-8.0",
@ -12519,7 +12522,8 @@
"flutter-3.24",
"flutter-3.27",
"flutter-3.29",
"flutter-3.32"
"flutter-3.32",
"flutter-3.35"
],
"x-enum-name": null,
"x-enum-keys": []
@ -13118,11 +13122,24 @@
"default": null,
"x-example": "<ROOT_DIRECTORY>"
},
"version": {
"type": {
"type": "string",
"description": "Version (tag) for the repo linked to the function template.",
"description": "Type for the reference provided. Can be commit, branch, or tag",
"default": null,
"x-example": "<VERSION>"
"x-example": "commit",
"enum": [
"commit",
"branch",
"tag"
],
"x-enum-name": null,
"x-enum-keys": []
},
"reference": {
"type": "string",
"description": "Reference value, can be a commit hash, branch name, or release tag",
"default": null,
"x-example": "<REFERENCE>"
},
"activate": {
"type": "boolean",
@ -13135,7 +13152,8 @@
"repository",
"owner",
"rootDirectory",
"version"
"type",
"reference"
]
}
}
@ -21845,6 +21863,7 @@
"dart-3.3",
"dart-3.5",
"dart-3.8",
"dart-3.9",
"dotnet-6.0",
"dotnet-7.0",
"dotnet-8.0",
@ -21871,7 +21890,8 @@
"flutter-3.24",
"flutter-3.27",
"flutter-3.29",
"flutter-3.32"
"flutter-3.32",
"flutter-3.35"
],
"x-enum-name": null,
"x-enum-keys": []
@ -22277,6 +22297,7 @@
"dart-3.3",
"dart-3.5",
"dart-3.8",
"dart-3.9",
"dotnet-6.0",
"dotnet-7.0",
"dotnet-8.0",
@ -22303,7 +22324,8 @@
"flutter-3.24",
"flutter-3.27",
"flutter-3.29",
"flutter-3.32"
"flutter-3.32",
"flutter-3.35"
],
"x-enum-name": null,
"x-enum-keys": []
@ -22859,11 +22881,24 @@
"default": null,
"x-example": "<ROOT_DIRECTORY>"
},
"version": {
"type": {
"type": "string",
"description": "Version (tag) for the repo linked to the site template.",
"description": "Type for the reference provided. Can be commit, branch, or tag",
"default": null,
"x-example": "<VERSION>"
"x-example": "branch",
"enum": [
"branch",
"commit",
"tag"
],
"x-enum-name": null,
"x-enum-keys": []
},
"reference": {
"type": "string",
"description": "Reference value, can be a commit hash, branch name, or release tag",
"default": null,
"x-example": "<REFERENCE>"
},
"activate": {
"type": "boolean",
@ -22876,7 +22911,8 @@
"repository",
"owner",
"rootDirectory",
"version"
"type",
"reference"
]
}
}

View file

@ -14,7 +14,7 @@ return [
],
'DART' => [
'name' => 'dart',
'versions' => ['3.8', '3.5', '3.3', '3.1', '3.0', '2.19', '2.18', '2.17', '2.16']
'versions' => ['3.9', '3.8', '3.5', '3.3', '3.1', '3.0', '2.19', '2.18', '2.17', '2.16']
],
'GO' => [
'name' => 'go',
@ -38,6 +38,6 @@ return [
],
'FLUTTER' => [
'name' => 'flutter',
'versions' => ['3.32', '3.24']
'versions' => ['3.35', '3.32', '3.24']
],
];

View file

@ -84,7 +84,7 @@ const TEMPLATE_FRAMEWORKS = [
'installCommand' => '',
'buildCommand' => 'flutter build web',
'outputDirectory' => './build/web',
'buildRuntime' => 'flutter-3.29',
'buildRuntime' => 'flutter-3.35',
'adapter' => 'static',
'fallbackFile' => '',
],

View file

@ -23,6 +23,7 @@ use Appwrite\Utopia\Request\Filters\V17 as RequestV17;
use Appwrite\Utopia\Request\Filters\V18 as RequestV18;
use Appwrite\Utopia\Request\Filters\V19 as RequestV19;
use Appwrite\Utopia\Request\Filters\V20 as RequestV20;
use Appwrite\Utopia\Request\Filters\V21 as RequestV21;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Filters\V16 as ResponseV16;
use Appwrite\Utopia\Response\Filters\V17 as ResponseV17;
@ -906,6 +907,9 @@ App::init()
$dbForProject = $getProjectDB($project);
$request->addFilter(new RequestV20($dbForProject, $route->getPathValues($request)));
}
if (version_compare($requestFormat, '1.9.0', '<')) {
$request->addFilter(new RequestV21());
}
}
$domain = $request->getHostname();

38
composer.lock generated
View file

@ -756,16 +756,16 @@
},
{
"name": "google/protobuf",
"version": "v4.33.0",
"version": "v4.33.1",
"source": {
"type": "git",
"url": "https://github.com/protocolbuffers/protobuf-php.git",
"reference": "b50269e23204e5ae859a326ec3d90f09efe3047d"
"reference": "0cd73ccf0cd26c3e72299cce1ea6144091a57e12"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/b50269e23204e5ae859a326ec3d90f09efe3047d",
"reference": "b50269e23204e5ae859a326ec3d90f09efe3047d",
"url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/0cd73ccf0cd26c3e72299cce1ea6144091a57e12",
"reference": "0cd73ccf0cd26c3e72299cce1ea6144091a57e12",
"shasum": ""
},
"require": {
@ -794,9 +794,9 @@
"proto"
],
"support": {
"source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.33.0"
"source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.33.1"
},
"time": "2025-10-15T20:10:28+00:00"
"time": "2025-11-12T21:58:05+00:00"
},
{
"name": "league/csv",
@ -3844,16 +3844,16 @@
},
{
"name": "utopia-php/database",
"version": "3.2.0",
"version": "3.4.0",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/database.git",
"reference": "f2d01b6b38057891184f62107bf70a55bc2ea068"
"reference": "e10b4faa4f3a3ef30a5f6d76acdb605469924aec"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/database/zipball/f2d01b6b38057891184f62107bf70a55bc2ea068",
"reference": "f2d01b6b38057891184f62107bf70a55bc2ea068",
"url": "https://api.github.com/repos/utopia-php/database/zipball/e10b4faa4f3a3ef30a5f6d76acdb605469924aec",
"reference": "e10b4faa4f3a3ef30a5f6d76acdb605469924aec",
"shasum": ""
},
"require": {
@ -3895,10 +3895,10 @@
"utopia"
],
"support": {
"source": "https://github.com/utopia-php/database/tree/3.2.0",
"issues": "https://github.com/utopia-php/database/issues"
"issues": "https://github.com/utopia-php/database/issues",
"source": "https://github.com/utopia-php/database/tree/3.4.0"
},
"time": "2025-11-06T05:41:54+00:00"
"time": "2025-11-13T06:34:20+00:00"
},
{
"name": "utopia-php/detector",
@ -5383,16 +5383,16 @@
"packages-dev": [
{
"name": "appwrite/sdk-generator",
"version": "1.5.3",
"version": "1.5.4",
"source": {
"type": "git",
"url": "https://github.com/appwrite/sdk-generator.git",
"reference": "1a7a3b89147aa8c1bde5247f8eeb7e4832c6016d"
"reference": "958947b6483a79e11c3812f23bb3056199fa4105"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/1a7a3b89147aa8c1bde5247f8eeb7e4832c6016d",
"reference": "1a7a3b89147aa8c1bde5247f8eeb7e4832c6016d",
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/958947b6483a79e11c3812f23bb3056199fa4105",
"reference": "958947b6483a79e11c3812f23bb3056199fa4105",
"shasum": ""
},
"require": {
@ -5428,9 +5428,9 @@
"description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms",
"support": {
"issues": "https://github.com/appwrite/sdk-generator/issues",
"source": "https://github.com/appwrite/sdk-generator/tree/1.5.3"
"source": "https://github.com/appwrite/sdk-generator/tree/1.5.4"
},
"time": "2025-11-10T09:50:41+00:00"
"time": "2025-11-12T12:43:42+00:00"
},
{
"name": "doctrine/annotations",

View file

@ -0,0 +1,41 @@
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Avatars;
Client client = new Client(context)
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>"); // Your project ID
Avatars avatars = new Avatars(client);
avatars.getScreenshot(
"https://example.com", // url
mapOf( "a" to "b" ), // headers (optional)
1, // viewportWidth (optional)
1, // viewportHeight (optional)
0.1, // scale (optional)
theme.LIGHT, // theme (optional)
"<USER_AGENT>", // userAgent (optional)
false, // fullpage (optional)
"<LOCALE>", // locale (optional)
timezone.AFRICA_ABIDJAN, // timezone (optional)
-90, // latitude (optional)
-180, // longitude (optional)
0, // accuracy (optional)
false, // touch (optional)
listOf(), // permissions (optional)
0, // sleep (optional)
0, // width (optional)
0, // height (optional)
-1, // quality (optional)
output.JPG, // output (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
return;
}
Log.d("Appwrite", result.toString());
})
);

View file

@ -0,0 +1,32 @@
import io.appwrite.Client
import io.appwrite.coroutines.CoroutineCallback
import io.appwrite.services.Avatars
val client = Client(context)
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>") // Your project ID
val avatars = Avatars(client)
val result = avatars.getScreenshot(
url = "https://example.com",
headers = mapOf( "a" to "b" ), // (optional)
viewportWidth = 1, // (optional)
viewportHeight = 1, // (optional)
scale = 0.1, // (optional)
theme = theme.LIGHT, // (optional)
userAgent = "<USER_AGENT>", // (optional)
fullpage = false, // (optional)
locale = "<LOCALE>", // (optional)
timezone = timezone.AFRICA_ABIDJAN, // (optional)
latitude = -90, // (optional)
longitude = -180, // (optional)
accuracy = 0, // (optional)
touch = false, // (optional)
permissions = listOf(), // (optional)
sleep = 0, // (optional)
width = 0, // (optional)
height = 0, // (optional)
quality = -1, // (optional)
output = output.JPG, // (optional)
)

View file

@ -0,0 +1,32 @@
import Appwrite
import AppwriteEnums
let client = Client()
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>") // Your project ID
let avatars = Avatars(client)
let bytes = try await avatars.getScreenshot(
url: "https://example.com",
headers: [:], // optional
viewportWidth: 1, // optional
viewportHeight: 1, // optional
scale: 0.1, // optional
theme: .light, // optional
userAgent: "<USER_AGENT>", // optional
fullpage: false, // optional
locale: "<LOCALE>", // optional
timezone: .africaAbidjan, // optional
latitude: -90, // optional
longitude: -180, // optional
accuracy: 0, // optional
touch: false, // optional
permissions: [], // optional
sleep: 0, // optional
width: 0, // optional
height: 0, // optional
quality: -1, // optional
output: .jpg // optional
)

View file

@ -0,0 +1,65 @@
import 'package:appwrite/appwrite.dart';
Client client = Client()
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
.setProject('<YOUR_PROJECT_ID>'); // Your project ID
Avatars avatars = Avatars(client);
// Downloading file
UInt8List bytes = await avatars.getScreenshot(
url: 'https://example.com',
headers: {}, // optional
viewportWidth: 1, // optional
viewportHeight: 1, // optional
scale: 0.1, // optional
theme: .light, // optional
userAgent: '<USER_AGENT>', // optional
fullpage: false, // optional
locale: '<LOCALE>', // optional
timezone: .africaAbidjan, // optional
latitude: -90, // optional
longitude: -180, // optional
accuracy: 0, // optional
touch: false, // optional
permissions: [], // optional
sleep: 0, // optional
width: 0, // optional
height: 0, // optional
quality: -1, // optional
output: .jpg, // optional
)
final file = File('path_to_file/filename.ext');
file.writeAsBytesSync(bytes);
// Displaying image preview
FutureBuilder(
future: avatars.getScreenshot(
url:'https://example.com' ,
headers:{} , // optional
viewportWidth:1 , // optional
viewportHeight:1 , // optional
scale:0.1 , // optional
theme: .light, // optional
userAgent:'<USER_AGENT>' , // optional
fullpage:false , // optional
locale:'<LOCALE>' , // optional
timezone: .africaAbidjan, // optional
latitude:-90 , // optional
longitude:-180 , // optional
accuracy:0 , // optional
touch:false , // optional
permissions:[] , // optional
sleep:0 , // optional
width:0 , // optional
height:0 , // optional
quality:-1 , // optional
output: .jpg, // optional
), // Works for both public file and private file, for private files you need to be logged in
builder: (context, snapshot) {
return snapshot.hasData && snapshot.data != null
? Image.memory(snapshot.data)
: CircularProgressIndicator();
}
);

View file

@ -0,0 +1,32 @@
import { Client, Avatars, , , } from "react-native-appwrite";
const client = new Client()
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
.setProject('<YOUR_PROJECT_ID>'); // Your project ID
const avatars = new Avatars(client);
const result = avatars.getScreenshot({
url: 'https://example.com',
headers: {}, // optional
viewportWidth: 1, // optional
viewportHeight: 1, // optional
scale: 0.1, // optional
theme: .Light, // optional
userAgent: '<USER_AGENT>', // optional
fullpage: false, // optional
locale: '<LOCALE>', // optional
timezone: .AfricaAbidjan, // optional
latitude: -90, // optional
longitude: -180, // optional
accuracy: 0, // optional
touch: false, // optional
permissions: [], // optional
sleep: 0, // optional
width: 0, // optional
height: 0, // optional
quality: -1, // optional
output: .Jpg // optional
});
console.log(result);

View file

@ -0,0 +1,6 @@
GET /v1/avatars/screenshots HTTP/1.1
Host: cloud.appwrite.io
X-Appwrite-Response-Format: 1.8.0
X-Appwrite-Project: <YOUR_PROJECT_ID>
X-Appwrite-Session:
X-Appwrite-JWT: <YOUR_JWT>

View file

@ -0,0 +1,32 @@
import { Client, Avatars, , , } from "appwrite";
const client = new Client()
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
.setProject('<YOUR_PROJECT_ID>'); // Your project ID
const avatars = new Avatars(client);
const result = avatars.getScreenshot({
url: 'https://example.com',
headers: {}, // optional
viewportWidth: 1, // optional
viewportHeight: 1, // optional
scale: 0.1, // optional
theme: .Light, // optional
userAgent: '<USER_AGENT>', // optional
fullpage: false, // optional
locale: '<LOCALE>', // optional
timezone: .AfricaAbidjan, // optional
latitude: -90, // optional
longitude: -180, // optional
accuracy: 0, // optional
touch: false, // optional
permissions: [], // optional
sleep: 0, // optional
width: 0, // optional
height: 0, // optional
quality: -1, // optional
output: .Jpg // optional
});
console.log(result);

View file

@ -0,0 +1,32 @@
import { Client, Avatars, , , } from "@appwrite.io/console";
const client = new Client()
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
.setProject('<YOUR_PROJECT_ID>'); // Your project ID
const avatars = new Avatars(client);
const result = avatars.getScreenshot({
url: 'https://example.com',
headers: {}, // optional
viewportWidth: 1, // optional
viewportHeight: 1, // optional
scale: 0.1, // optional
theme: .Light, // optional
userAgent: '<USER_AGENT>', // optional
fullpage: false, // optional
locale: '<LOCALE>', // optional
timezone: .AfricaAbidjan, // optional
latitude: -90, // optional
longitude: -180, // optional
accuracy: 0, // optional
touch: false, // optional
permissions: [], // optional
sleep: 0, // optional
width: 0, // optional
height: 0, // optional
quality: -1, // optional
output: .Jpg // optional
});
console.log(result);

View file

@ -0,0 +1,31 @@
import 'package:dart_appwrite/dart_appwrite.dart';
Client client = Client()
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
.setProject('<YOUR_PROJECT_ID>') // Your project ID
.setSession(''); // The user session to authenticate with
Avatars avatars = Avatars(client);
UInt8List result = await avatars.getScreenshot(
url: 'https://example.com',
headers: {}, // (optional)
viewportWidth: 1, // (optional)
viewportHeight: 1, // (optional)
scale: 0.1, // (optional)
theme: .light, // (optional)
userAgent: '<USER_AGENT>', // (optional)
fullpage: false, // (optional)
locale: '<LOCALE>', // (optional)
timezone: .africaAbidjan, // (optional)
latitude: -90, // (optional)
longitude: -180, // (optional)
accuracy: 0, // (optional)
touch: false, // (optional)
permissions: [], // (optional)
sleep: 0, // (optional)
width: 0, // (optional)
height: 0, // (optional)
quality: -1, // (optional)
output: .jpg, // (optional)
);

View file

@ -0,0 +1,34 @@
using Appwrite;
using Appwrite.Enums;
using Appwrite.Models;
using Appwrite.Services;
Client client = new Client()
.SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
.SetProject("<YOUR_PROJECT_ID>") // Your project ID
.SetSession(""); // The user session to authenticate with
Avatars avatars = new Avatars(client);
byte[] result = await avatars.GetScreenshot(
url: "https://example.com",
headers: [object], // optional
viewportWidth: 1, // optional
viewportHeight: 1, // optional
scale: 0.1, // optional
theme: .Light, // optional
userAgent: "<USER_AGENT>", // optional
fullpage: false, // optional
locale: "<LOCALE>", // optional
timezone: .AfricaAbidjan, // optional
latitude: -90, // optional
longitude: -180, // optional
accuracy: 0, // optional
touch: false, // optional
permissions: new List<string>(), // optional
sleep: 0, // optional
width: 0, // optional
height: 0, // optional
quality: -1, // optional
output: .Jpg // optional
);

View file

@ -0,0 +1,38 @@
package main
import (
"fmt"
"github.com/appwrite/sdk-for-go/client"
"github.com/appwrite/sdk-for-go/avatars"
)
client := client.New(
client.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1")
client.WithProject("<YOUR_PROJECT_ID>")
client.WithSession("")
)
service := avatars.New(client)
response, error := service.GetScreenshot(
"https://example.com",
avatars.WithGetScreenshotHeaders(map[string]interface{}{}),
avatars.WithGetScreenshotViewportWidth(1),
avatars.WithGetScreenshotViewportHeight(1),
avatars.WithGetScreenshotScale(0.1),
avatars.WithGetScreenshotTheme("light"),
avatars.WithGetScreenshotUserAgent("<USER_AGENT>"),
avatars.WithGetScreenshotFullpage(false),
avatars.WithGetScreenshotLocale("<LOCALE>"),
avatars.WithGetScreenshotTimezone("africa/abidjan"),
avatars.WithGetScreenshotLatitude(-90),
avatars.WithGetScreenshotLongitude(-180),
avatars.WithGetScreenshotAccuracy(0),
avatars.WithGetScreenshotTouch(false),
avatars.WithGetScreenshotPermissions([]interface{}{}),
avatars.WithGetScreenshotSleep(0),
avatars.WithGetScreenshotWidth(0),
avatars.WithGetScreenshotHeight(0),
avatars.WithGetScreenshotQuality(-1),
avatars.WithGetScreenshotOutput("jpg"),
)

View file

@ -0,0 +1,42 @@
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Avatars;
Client client = new Client()
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>") // Your project ID
.setSession(""); // The user session to authenticate with
Avatars avatars = new Avatars(client);
avatars.getScreenshot(
"https://example.com", // url
mapOf( "a" to "b" ), // headers (optional)
1, // viewportWidth (optional)
1, // viewportHeight (optional)
0.1, // scale (optional)
.LIGHT, // theme (optional)
"<USER_AGENT>", // userAgent (optional)
false, // fullpage (optional)
"<LOCALE>", // locale (optional)
.AFRICA_ABIDJAN, // timezone (optional)
-90, // latitude (optional)
-180, // longitude (optional)
0, // accuracy (optional)
false, // touch (optional)
listOf(), // permissions (optional)
0, // sleep (optional)
0, // width (optional)
0, // height (optional)
-1, // quality (optional)
.JPG, // output (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
return;
}
System.out.println(result);
})
);

View file

@ -0,0 +1,33 @@
import io.appwrite.Client
import io.appwrite.coroutines.CoroutineCallback
import io.appwrite.services.Avatars
val client = Client()
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>") // Your project ID
.setSession("") // The user session to authenticate with
val avatars = Avatars(client)
val result = avatars.getScreenshot(
url = "https://example.com",
headers = mapOf( "a" to "b" ), // optional
viewportWidth = 1, // optional
viewportHeight = 1, // optional
scale = 0.1, // optional
theme = "light", // optional
userAgent = "<USER_AGENT>", // optional
fullpage = false, // optional
locale = "<LOCALE>", // optional
timezone = "africa/abidjan", // optional
latitude = -90, // optional
longitude = -180, // optional
accuracy = 0, // optional
touch = false, // optional
permissions = listOf(), // optional
sleep = 0, // optional
width = 0, // optional
height = 0, // optional
quality = -1, // optional
output = "jpg" // optional
)

View file

@ -0,0 +1,31 @@
const sdk = require('node-appwrite');
const client = new sdk.Client()
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
.setProject('<YOUR_PROJECT_ID>') // Your project ID
.setSession(''); // The user session to authenticate with
const avatars = new sdk.Avatars(client);
const result = await avatars.getScreenshot({
url: 'https://example.com',
headers: {}, // optional
viewportWidth: 1, // optional
viewportHeight: 1, // optional
scale: 0.1, // optional
theme: sdk..Light, // optional
userAgent: '<USER_AGENT>', // optional
fullpage: false, // optional
locale: '<LOCALE>', // optional
timezone: sdk..AfricaAbidjan, // optional
latitude: -90, // optional
longitude: -180, // optional
accuracy: 0, // optional
touch: false, // optional
permissions: [], // optional
sleep: 0, // optional
width: 0, // optional
height: 0, // optional
quality: -1, // optional
output: sdk..Jpg // optional
});

View file

@ -0,0 +1,32 @@
from appwrite.client import Client
from appwrite.services.avatars import Avatars
client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint
client.set_project('<YOUR_PROJECT_ID>') # Your project ID
client.set_session('') # The user session to authenticate with
avatars = Avatars(client)
result = avatars.get_screenshot(
url = 'https://example.com',
headers = {}, # optional
viewport_width = 1, # optional
viewport_height = 1, # optional
scale = 0.1, # optional
theme = .LIGHT, # optional
user_agent = '<USER_AGENT>', # optional
fullpage = False, # optional
locale = '<LOCALE>', # optional
timezone = .AFRICA_ABIDJAN, # optional
latitude = -90, # optional
longitude = -180, # optional
accuracy = 0, # optional
touch = False, # optional
permissions = [], # optional
sleep = 0, # optional
width = 0, # optional
height = 0, # optional
quality = -1, # optional
output = .JPG # optional
)

View file

@ -0,0 +1,7 @@
GET /v1/avatars/screenshots HTTP/1.1
Host: cloud.appwrite.io
X-Appwrite-Response-Format: 1.8.0
X-Appwrite-Project: <YOUR_PROJECT_ID>
X-Appwrite-Session:
X-Appwrite-Key: <YOUR_API_KEY>
X-Appwrite-JWT: <YOUR_JWT>

View file

@ -0,0 +1,33 @@
require 'appwrite'
include Appwrite
client = Client.new
.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint
.set_project('<YOUR_PROJECT_ID>') # Your project ID
.set_session('') # The user session to authenticate with
avatars = Avatars.new(client)
result = avatars.get_screenshot(
url: 'https://example.com',
headers: {}, # optional
viewport_width: 1, # optional
viewport_height: 1, # optional
scale: 0.1, # optional
theme: ::LIGHT, # optional
user_agent: '<USER_AGENT>', # optional
fullpage: false, # optional
locale: '<LOCALE>', # optional
timezone: ::AFRICA_ABIDJAN, # optional
latitude: -90, # optional
longitude: -180, # optional
accuracy: 0, # optional
touch: false, # optional
permissions: [], # optional
sleep: 0, # optional
width: 0, # optional
height: 0, # optional
quality: -1, # optional
output: ::JPG # optional
)

View file

@ -0,0 +1,33 @@
import Appwrite
import AppwriteEnums
let client = Client()
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>") // Your project ID
.setSession("") // The user session to authenticate with
let avatars = Avatars(client)
let bytes = try await avatars.getScreenshot(
url: "https://example.com",
headers: [:], // optional
viewportWidth: 1, // optional
viewportHeight: 1, // optional
scale: 0.1, // optional
theme: .light, // optional
userAgent: "<USER_AGENT>", // optional
fullpage: false, // optional
locale: "<LOCALE>", // optional
timezone: .africaAbidjan, // optional
latitude: -90, // optional
longitude: -180, // optional
accuracy: 0, // optional
touch: false, // optional
permissions: [], // optional
sleep: 0, // optional
width: 0, // optional
height: 0, // optional
quality: -1, // optional
output: .jpg // optional
)

View file

@ -1,5 +1,12 @@
# Change Log
## 19.4.0
* Add `getScreenshot` method to `Avatars` service
* Add enums `Theme`, `Output` and `Timezone`
* Update runtime enums to add support for `dart39` and `flutter335` runtimes
* Fix passing of `null` values and stripping only non-nullable optional parameters from the request body
## 19.3.0
* Add `total` parameter to list queries allowing skipping counting rows in a table for improved performance

View file

@ -1,5 +1,9 @@
# Change Log
## 20.3.1
* Fix passing of `null` values and stripping only non-nullable optional parameters from the request body
## 20.3.0
* Add `total` parameter to list queries allowing skipping counting rows in a table for improved performance

View file

@ -31,6 +31,7 @@
<directory>./tests/e2e/Services/Locale</directory>
<directory>./tests/e2e/Services/Projects</directory>
<directory>./tests/e2e/Services/Storage</directory>
<directory>./tests/e2e/Services/Tokens</directory>
<directory>./tests/e2e/Services/Webhooks</directory>
<directory>./tests/e2e/Services/Messaging</directory>
<directory>./tests/e2e/Services/Migrations</directory>

View file

@ -21,6 +21,7 @@ use Utopia\Platform\Scope\HTTP;
use Utopia\Swoole\Request;
use Utopia\Validator\Boolean;
use Utopia\Validator\Text;
use Utopia\Validator\WhiteList;
use Utopia\VCS\Adapter\Git\GitHub;
class Create extends Base
@ -65,7 +66,8 @@ class Create extends Base
->param('repository', '', new Text(128, 0), 'Repository name of the template.')
->param('owner', '', new Text(128, 0), 'The name of the owner of the template.')
->param('rootDirectory', '', new Text(128, 0), 'Path to function code in the template repo.')
->param('version', '', new Text(128, 0), 'Version (tag) for the repo linked to the function template.')
->param('type', '', new WhiteList(['commit', 'branch', 'tag']), 'Type for the reference provided. Can be commit, branch, or tag')
->param('reference', '', new Text(128, 0), 'Reference value, can be a commit hash, branch name, or release tag')
->param('activate', false, new Boolean(), 'Automatically activate the deployment when it is finished building.', true)
->inject('request')
->inject('response')
@ -83,7 +85,8 @@ class Create extends Base
string $repository,
string $owner,
string $rootDirectory,
string $version,
string $type,
string $reference,
bool $activate,
Request $request,
Response $response,
@ -100,11 +103,16 @@ class Create extends Base
throw new Exception(Exception::FUNCTION_NOT_FOUND);
}
$branchUrl = "https://github.com/$owner/$repository/blob/$reference";
$repositoryUrl = "https://github.com/$owner/$repository";
$template = new Document([
'repositoryName' => $repository,
'ownerName' => $owner,
'rootDirectory' => $rootDirectory,
'version' => $version
'referenceType' => $type,
'referenceValue' => $reference,
]);
if (!empty($function->getAttribute('providerRepositoryId'))) {
@ -146,7 +154,12 @@ class Create extends Base
'resourceType' => 'functions',
'entrypoint' => $function->getAttribute('entrypoint', ''),
'buildCommands' => $function->getAttribute('commands', ''),
'type' => 'manual',
'providerRepositoryName' => $repository,
'providerRepositoryOwner' => $owner,
'providerRepositoryUrl' => $repositoryUrl,
'providerBranchUrl' => $branchUrl,
'providerBranch' => $type == GitHub::CLONE_TYPE_BRANCH ? $reference : '',
'type' => 'vcs',
'activate' => $activate,
]));

View file

@ -310,20 +310,23 @@ class Builds extends Action
// Non-VCS + Template
$templateRepositoryName = $template->getAttribute('repositoryName', '');
$templateOwnerName = $template->getAttribute('ownerName', '');
$templateVersion = $template->getAttribute('version', '');
$templateReferenceType = $template->getAttribute('referenceType', '');
$templateReferenceValue = $template->getAttribute('referenceValue', '');
$templateRootDirectory = $template->getAttribute('rootDirectory', '');
$templateRootDirectory = \rtrim($templateRootDirectory, '/');
$templateRootDirectory = \ltrim($templateRootDirectory, '.');
$templateRootDirectory = \ltrim($templateRootDirectory, '/');
if (!empty($templateRepositoryName) && !empty($templateOwnerName) && !empty($templateVersion)) {
if (!empty($templateRepositoryName) && !empty($templateOwnerName) && !empty($templateReferenceType) && !empty($templateReferenceValue)) {
$stdout = '';
$stderr = '';
// Clone template repo
$tmpTemplateDirectory = '/tmp/builds/' . $deploymentId . '-template';
$gitCloneCommandForTemplate = $github->generateCloneCommand($templateOwnerName, $templateRepositoryName, $templateVersion, GitHub::CLONE_TYPE_TAG, $tmpTemplateDirectory, $templateRootDirectory);
$gitCloneCommandForTemplate = $github->generateCloneCommand($templateOwnerName, $templateRepositoryName, $templateReferenceValue, $templateReferenceType, $tmpTemplateDirectory, $templateRootDirectory);
$exit = Console::execute($gitCloneCommandForTemplate, '', $stdout, $stderr);
if ($exit !== 0) {

View file

@ -23,6 +23,7 @@ use Utopia\Swoole\Request;
use Utopia\System\System;
use Utopia\Validator\Boolean;
use Utopia\Validator\Text;
use Utopia\Validator\WhiteList;
use Utopia\VCS\Adapter\Git\GitHub;
class Create extends Base
@ -67,7 +68,8 @@ class Create extends Base
->param('repository', '', new Text(128, 0), 'Repository name of the template.')
->param('owner', '', new Text(128, 0), 'The name of the owner of the template.')
->param('rootDirectory', '', new Text(128, 0), 'Path to site code in the template repo.')
->param('version', '', new Text(128, 0), 'Version (tag) for the repo linked to the site template.')
->param('type', '', new WhiteList(['branch', 'commit', 'tag']), 'Type for the reference provided. Can be commit, branch, or tag')
->param('reference', '', new Text(128, 0), 'Reference value, can be a commit hash, branch name, or release tag')
->param('activate', false, new Boolean(), 'Automatically activate the deployment when it is finished building.', true)
->inject('request')
->inject('response')
@ -85,7 +87,8 @@ class Create extends Base
string $repository,
string $owner,
string $rootDirectory,
string $version,
string $type,
string $reference,
bool $activate,
Request $request,
Response $response,
@ -102,11 +105,15 @@ class Create extends Base
throw new Exception(Exception::SITE_NOT_FOUND);
}
$branchUrl = "https://github.com/$owner/$repository/blob/$reference";
$repositoryUrl = "https://github.com/$owner/$repository";
$template = new Document([
'repositoryName' => $repository,
'ownerName' => $owner,
'rootDirectory' => $rootDirectory,
'version' => $version
'referenceType' => $type,
'referenceValue' => $reference
]);
if (!empty($site->getAttribute('providerRepositoryId'))) {
@ -157,9 +164,14 @@ class Create extends Base
'resourceType' => 'sites',
'buildCommands' => \implode(' && ', $commands),
'buildOutput' => $site->getAttribute('outputDirectory', ''),
'providerRepositoryName' => $repository,
'providerRepositoryOwner' => $owner,
'providerRepositoryUrl' => $repositoryUrl,
'providerBranchUrl' => $branchUrl,
'providerBranch' => $type == GitHub::CLONE_TYPE_BRANCH ? $reference : '',
'adapter' => $site->getAttribute('adapter', ''),
'fallbackFile' => $site->getAttribute('fallbackFile', ''),
'type' => 'manual',
'type' => 'vcs',
'activate' => $activate,
]));

View file

@ -61,7 +61,7 @@ class Create extends Action
))
->param('bucketId', '', new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](https://appwrite.io/docs/server/storage#createBucket).')
->param('fileId', '', new UID(), 'File unique ID.')
->param('expire', null, new Nullable(new DatetimeValidator()), 'Token expiry date', true)
->param('expire', null, new Nullable(new DatetimeValidator(requireDateInFuture: true)), 'Token expiry date', true)
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
@ -70,7 +70,6 @@ class Create extends Action
public function action(string $bucketId, string $fileId, ?string $expire, Response $response, Database $dbForProject, Event $queueForEvents): void
{
/**
* @var Document $bucket
* @var Document $file

View file

@ -57,7 +57,7 @@ class Update extends Action
contentType: ContentType::JSON
))
->param('tokenId', '', new UID(), 'Token unique ID.')
->param('expire', null, new Nullable(new DatetimeValidator()), 'File token expiry date', true)
->param('expire', null, new Nullable(new DatetimeValidator(requireDateInFuture: true)), 'File token expiry date', true)
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')

View file

@ -0,0 +1,34 @@
<?php
namespace Appwrite\Utopia\Request\Filters;
use Appwrite\Utopia\Request\Filter;
class V21 extends Filter
{
// Convert 1.8.0 params to 1.8.1
public function parse(array $content, string $model): array
{
switch ($model) {
case 'functions.createTemplateDeployment':
case 'sites.createTemplateDeployment':
$content = $this->convertVersionToTypeAndReference($content);
break;
}
return $content;
}
/**
* Convert version parameter to type and reference for backwards compatibility
* with 1.8.0 template deployment endpoints
*/
protected function convertVersionToTypeAndReference(array $content): array
{
if (!empty($content['version'])) {
$content['type'] = 'tag';
$content['reference'] = $content['version'];
unset($content['version']);
}
return $content;
}
}

View file

@ -28,6 +28,7 @@ class UsageTest extends Scope
FunctionsBase::createVariable insteadof SitesBase;
FunctionsBase::getVariable insteadof SitesBase;
FunctionsBase::listVariables insteadof SitesBase;
FunctionsBase::helperGetLatestCommit insteadof SitesBase;
FunctionsBase::updateVariable insteadof SitesBase;
FunctionsBase::deleteVariable insteadof SitesBase;
FunctionsBase::getDeployment insteadof SitesBase;

View file

@ -271,6 +271,29 @@ trait FunctionsBase
return $template;
}
protected function helperGetLatestCommit(string $owner, string $repository): ?string
{
$ch = curl_init("https://api.github.com/repos/{$owner}/{$repository}/commits/main");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'User-Agent: Appwrite',
'Accept: application/vnd.github.v3+json'
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode === 200) {
$commitData = json_decode($response, true);
if (isset($commitData['sha'])) {
return $commitData['sha'];
}
}
return null;
}
protected function createExecution(string $functionId, mixed $params = []): mixed
{
$execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([

View file

@ -361,7 +361,7 @@ class FunctionsCustomServerTest extends Scope
$starterTemplate = $this->getTemplate('starter');
$this->assertEquals(200, $starterTemplate['headers']['status-code']);
$phpRuntime = array_values(array_filter($starterTemplate['body']['runtimes'], function ($runtime) {
$runtime = array_values(array_filter($starterTemplate['body']['runtimes'], function ($runtime) {
return $runtime['name'] === 'node-22';
}))[0];
@ -374,15 +374,15 @@ class FunctionsCustomServerTest extends Scope
'name' => $starterTemplate['body']['name'],
'runtime' => 'node-22',
'execute' => $starterTemplate['body']['permissions'],
'entrypoint' => $phpRuntime['entrypoint'],
'entrypoint' => $runtime['entrypoint'],
'events' => $starterTemplate['body']['events'],
'schedule' => $starterTemplate['body']['cron'],
'timeout' => $starterTemplate['body']['timeout'],
'commands' => $phpRuntime['commands'],
'commands' => $runtime['commands'],
'scopes' => $starterTemplate['body']['scopes'],
'templateRepository' => $starterTemplate['body']['providerRepositoryId'],
'templateOwner' => $starterTemplate['body']['providerOwner'],
'templateRootDirectory' => $phpRuntime['providerRootDirectory'],
'templateRootDirectory' => $runtime['providerRootDirectory'],
'templateVersion' => $starterTemplate['body']['providerVersion'],
]
);
@ -399,19 +399,29 @@ class FunctionsCustomServerTest extends Scope
'activate' => true,
'repository' => $starterTemplate['body']['providerRepositoryId'],
'owner' => $starterTemplate['body']['providerOwner'],
'rootDirectory' => $phpRuntime['providerRootDirectory'],
'version' => $starterTemplate['body']['providerVersion'],
'rootDirectory' => $runtime['providerRootDirectory'],
'type' => 'tag',
'reference' => $starterTemplate['body']['providerVersion'],
]
);
$this->assertEquals(202, $deployment['headers']['status-code']);
$this->assertNotEmpty($deployment['body']['$id']);
$deployment = $this->getDeployment($functionId, $deployment['body']['$id']);
// Wait for deployment to be ready
$deploymentId = $deployment['body']['$id'];
$this->assertEventually(function () use ($functionId, $deploymentId) {
$deployment = $this->getDeployment($functionId, $deploymentId);
$this->assertEquals('ready', $deployment['body']['status']);
}, 50000, 500);
// Verify deployment sizes
$deployment = $this->getDeployment($functionId, $deploymentId);
$this->assertEquals(200, $deployment['headers']['status-code']);
$this->assertEquals(0, $deployment['body']['sourceSize']);
$this->assertEquals(0, $deployment['body']['buildSize']);
$this->assertEquals(0, $deployment['body']['totalSize']);
$this->assertGreaterThan(0, $deployment['body']['sourceSize']);
$this->assertGreaterThan(0, $deployment['body']['buildSize']);
$totalSize = $deployment['body']['sourceSize'] + $deployment['body']['buildSize'];
$this->assertEquals($totalSize, $deployment['body']['totalSize']);
$deployments = $this->listDeployments($functionId);
@ -433,16 +443,7 @@ class FunctionsCustomServerTest extends Scope
$lastDeployment = $deployments['body']['deployments'][0];
$this->assertNotEmpty($lastDeployment['$id']);
$this->assertEquals(0, $lastDeployment['sourceSize']);
$deploymentId = $lastDeployment['$id'];
$this->assertEventually(function () use ($functionId, $deploymentId) {
$deployment = $this->getDeployment($functionId, $deploymentId);
$this->assertEquals(200, $deployment['headers']['status-code']);
$this->assertEquals('ready', $deployment['body']['status']);
}, 50000, 1000);
$this->assertGreaterThan(0, $lastDeployment['sourceSize']);
$function = $this->getFunction($functionId);
@ -511,7 +512,144 @@ class FunctionsCustomServerTest extends Scope
$this->assertEquals($deployment['body']['$id'], $function['body']['deploymentId']);
$this->assertEquals($deployment['body']['$createdAt'], $function['body']['deploymentCreatedAt']);
$function = $this->cleanupFunction($functionId);
$this->cleanupFunction($functionId);
}
public function testCreateFunctionAndDeploymentFromTemplateBranch()
{
$starterTemplate = $this->getTemplate('starter');
$this->assertEquals(200, $starterTemplate['headers']['status-code']);
$runtime = array_values(array_filter($starterTemplate['body']['runtimes'], function ($runtime) {
return $runtime['name'] === 'node-22';
}))[0];
// If this fails, the template has variables, and this test needs to be updated
$this->assertEmpty($starterTemplate['body']['variables']);
$function = $this->createFunction(
[
'functionId' => ID::unique(),
'name' => $starterTemplate['body']['name'] . ' - Branch Test',
'runtime' => 'node-22',
'execute' => $starterTemplate['body']['permissions'],
'entrypoint' => $runtime['entrypoint'],
'events' => $starterTemplate['body']['events'],
'schedule' => $starterTemplate['body']['cron'],
'timeout' => $starterTemplate['body']['timeout'],
'commands' => $runtime['commands'],
'scopes' => $starterTemplate['body']['scopes'],
]
);
$this->assertEquals(201, $function['headers']['status-code']);
$this->assertNotEmpty($function['body']['$id']);
$functionId = $function['body']['$id'] ?? '';
// Deploy using branch
$deployment = $this->createTemplateDeployment(
$functionId,
[
'resourceId' => ID::unique(),
'activate' => true,
'repository' => $starterTemplate['body']['providerRepositoryId'],
'owner' => $starterTemplate['body']['providerOwner'],
'rootDirectory' => $runtime['providerRootDirectory'],
'type' => 'branch',
'reference' => 'main',
]
);
$this->assertEquals(202, $deployment['headers']['status-code']);
$this->assertNotEmpty($deployment['body']['$id']);
$deploymentId = $deployment['body']['$id'];
$this->assertEventually(function () use ($functionId, $deploymentId) {
$deployment = $this->getDeployment($functionId, $deploymentId);
$this->assertEquals('ready', $deployment['body']['status']);
}, 50000, 500);
$deployment = $this->getDeployment($functionId, $deploymentId);
$this->assertEquals(200, $deployment['headers']['status-code']);
$this->assertGreaterThan(0, $deployment['body']['sourceSize']);
$this->assertGreaterThan(0, $deployment['body']['buildSize']);
$totalSize = $deployment['body']['sourceSize'] + $deployment['body']['buildSize'];
$this->assertEquals($totalSize, $deployment['body']['totalSize']);
$this->cleanupFunction($functionId);
}
public function testCreateFunctionAndDeploymentFromTemplateCommit()
{
$starterTemplate = $this->getTemplate('starter');
$this->assertEquals(200, $starterTemplate['headers']['status-code']);
// Get latest commit using helper function
$latestCommit = $this->helperGetLatestCommit(
$starterTemplate['body']['providerOwner'],
$starterTemplate['body']['providerRepositoryId']
);
$this->assertNotNull($latestCommit);
$runtime = array_values(array_filter($starterTemplate['body']['runtimes'], function ($runtime) {
return $runtime['name'] === 'node-22';
}))[0];
// If this fails, the template has variables, and this test needs to be updated
$this->assertEmpty($starterTemplate['body']['variables']);
$function = $this->createFunction(
[
'functionId' => ID::unique(),
'name' => $starterTemplate['body']['name'] . ' - Commit Test',
'runtime' => 'node-22',
'execute' => $starterTemplate['body']['permissions'],
'entrypoint' => $runtime['entrypoint'],
'events' => $starterTemplate['body']['events'],
'schedule' => $starterTemplate['body']['cron'],
'timeout' => $starterTemplate['body']['timeout'],
'commands' => $runtime['commands'],
'scopes' => $starterTemplate['body']['scopes'],
]
);
$this->assertEquals(201, $function['headers']['status-code']);
$this->assertNotEmpty($function['body']['$id']);
$functionId = $function['body']['$id'] ?? '';
// Deploy using commit
$deployment = $this->createTemplateDeployment(
$functionId,
[
'resourceId' => ID::unique(),
'activate' => true,
'repository' => $starterTemplate['body']['providerRepositoryId'],
'owner' => $starterTemplate['body']['providerOwner'],
'rootDirectory' => $runtime['providerRootDirectory'],
'type' => 'commit',
'reference' => $latestCommit,
]
);
$this->assertEquals(202, $deployment['headers']['status-code']);
$this->assertNotEmpty($deployment['body']['$id']);
$deploymentId = $deployment['body']['$id'];
$this->assertEventually(function () use ($functionId, $deploymentId) {
$deployment = $this->getDeployment($functionId, $deploymentId);
$this->assertEquals('ready', $deployment['body']['status']);
}, 50000, 500);
$deployment = $this->getDeployment($functionId, $deploymentId);
$this->assertEquals(200, $deployment['headers']['status-code']);
$this->assertGreaterThan(0, $deployment['body']['sourceSize']);
$this->assertGreaterThan(0, $deployment['body']['buildSize']);
$totalSize = $deployment['body']['sourceSize'] + $deployment['body']['buildSize'];
$this->assertEquals($totalSize, $deployment['body']['totalSize']);
$this->cleanupFunction($functionId);
}
/**

View file

@ -329,9 +329,33 @@ trait SitesBase
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
]);
return $template;
}
protected function helperGetLatestCommit(string $owner, string $repository): ?string
{
$ch = curl_init("https://api.github.com/repos/{$owner}/{$repository}/commits/main");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'User-Agent: Appwrite',
'Accept: application/vnd.github.v3+json'
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode === 200) {
$commitData = json_decode($response, true);
if (isset($commitData['sha'])) {
return $commitData['sha'];
}
}
return null;
}
protected function deleteSite(string $siteId): mixed
{
$site = $this->client->call(Client::METHOD_DELETE, '/sites/' . $siteId, array_merge([

View file

@ -1567,7 +1567,157 @@ class SitesCustomServerTest extends Scope
'repository' => $template['providerRepositoryId'],
'owner' => $template['providerOwner'],
'rootDirectory' => $template['frameworks'][0]['providerRootDirectory'],
'version' => $template['providerVersion'],
'type' => 'tag',
'reference' => $template['providerVersion'],
'activate' => true
]);
$this->assertEquals(202, $deployment['headers']['status-code']);
$this->assertNotEmpty($deployment['body']['$id']);
$deployment = $this->getDeployment($siteId, $deployment['body']['$id']);
$this->assertEquals(200, $deployment['headers']['status-code']);
$this->assertEquals(0, $deployment['body']['sourceSize']);
$this->assertEquals(0, $deployment['body']['buildSize']);
$this->assertEquals(0, $deployment['body']['totalSize']);
$this->assertEventually(function () use ($siteId) {
$site = $this->getSite($siteId);
$this->assertNotEmpty($site['body']['deploymentId']);
}, 50000, 500);
$domain = $this->setupSiteDomain($siteId);
$proxyClient = new Client();
$proxyClient->setEndpoint('http://' . $domain);
$response = $proxyClient->call(Client::METHOD_GET, '/');
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertStringContainsString("Astro Blog", $response['body']);
$this->assertStringContainsString("Hello, Astronaut!", $response['body']);
$response = $proxyClient->call(Client::METHOD_GET, '/about');
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertStringContainsString("Astro Blog", $response['body']);
$this->assertStringContainsString("About Me", $response['body']);
$deployment = $this->getDeployment($siteId, $deployment['body']['$id']);
$this->assertEquals(200, $deployment['headers']['status-code']);
$this->assertGreaterThan(0, $deployment['body']['sourceSize']);
$this->assertGreaterThan(0, $deployment['body']['buildSize']);
$totalSize = $deployment['body']['sourceSize'] + $deployment['body']['buildSize'];
$this->assertEquals($totalSize, $deployment['body']['totalSize']);
$this->cleanupSite($siteId);
}
public function testCreateSiteFromTemplateBranch()
{
$template = $this->getTemplate('playground-for-astro');
$this->assertEquals(200, $template['headers']['status-code']);
$template = $template['body'];
$siteId = $this->setupSite([
'siteId' => ID::unique(),
'name' => 'Astro Blog - Branch Test',
'framework' => $template['frameworks'][0]['key'],
'adapter' => $template['frameworks'][0]['adapter'],
'buildRuntime' => $template['frameworks'][0]['buildRuntime'],
'outputDirectory' => $template['frameworks'][0]['outputDirectory'],
'buildCommand' => $template['frameworks'][0]['buildCommand'],
'installCommand' => $template['frameworks'][0]['installCommand'],
'fallbackFile' => $template['frameworks'][0]['fallbackFile'],
]);
$this->assertNotEmpty($siteId);
// Deploy using branch
$deployment = $this->createTemplateDeployment($siteId, [
'repository' => $template['providerRepositoryId'],
'owner' => $template['providerOwner'],
'rootDirectory' => $template['frameworks'][0]['providerRootDirectory'],
'type' => 'branch',
'reference' => 'main',
'activate' => true
]);
$this->assertEquals(202, $deployment['headers']['status-code']);
$this->assertNotEmpty($deployment['body']['$id']);
$deployment = $this->getDeployment($siteId, $deployment['body']['$id']);
$this->assertEquals(200, $deployment['headers']['status-code']);
$this->assertEquals(0, $deployment['body']['sourceSize']);
$this->assertEquals(0, $deployment['body']['buildSize']);
$this->assertEquals(0, $deployment['body']['totalSize']);
$this->assertEventually(function () use ($siteId) {
$site = $this->getSite($siteId);
$this->assertNotEmpty($site['body']['deploymentId']);
}, 50000, 500);
$domain = $this->setupSiteDomain($siteId);
$proxyClient = new Client();
$proxyClient->setEndpoint('http://' . $domain);
$response = $proxyClient->call(Client::METHOD_GET, '/');
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertStringContainsString("Astro Blog", $response['body']);
$this->assertStringContainsString("Hello, Astronaut!", $response['body']);
$response = $proxyClient->call(Client::METHOD_GET, '/about');
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertStringContainsString("Astro Blog", $response['body']);
$this->assertStringContainsString("About Me", $response['body']);
$deployment = $this->getDeployment($siteId, $deployment['body']['$id']);
$this->assertEquals(200, $deployment['headers']['status-code']);
$this->assertGreaterThan(0, $deployment['body']['sourceSize']);
$this->assertGreaterThan(0, $deployment['body']['buildSize']);
$totalSize = $deployment['body']['sourceSize'] + $deployment['body']['buildSize'];
$this->assertEquals($totalSize, $deployment['body']['totalSize']);
$this->cleanupSite($siteId);
}
public function testCreateSiteFromTemplateCommit()
{
$template = $this->getTemplate('playground-for-astro');
$this->assertEquals(200, $template['headers']['status-code']);
// Get latest commit using helper function
$latestCommit = $this->helperGetLatestCommit(
$template['body']['providerOwner'],
$template['body']['providerRepositoryId']
);
$this->assertNotNull($latestCommit);
$template = $template['body'];
$siteId = $this->setupSite([
'siteId' => ID::unique(),
'name' => 'Astro Blog - Commit Test',
'framework' => $template['frameworks'][0]['key'],
'adapter' => $template['frameworks'][0]['adapter'],
'buildRuntime' => $template['frameworks'][0]['buildRuntime'],
'outputDirectory' => $template['frameworks'][0]['outputDirectory'],
'buildCommand' => $template['frameworks'][0]['buildCommand'],
'installCommand' => $template['frameworks'][0]['installCommand'],
'fallbackFile' => $template['frameworks'][0]['fallbackFile'],
]);
$this->assertNotEmpty($siteId);
// Deploy using commit
$deployment = $this->createTemplateDeployment($siteId, [
'repository' => $template['providerRepositoryId'],
'owner' => $template['providerOwner'],
'rootDirectory' => $template['frameworks'][0]['providerRootDirectory'],
'type' => 'commit',
'reference' => $latestCommit,
'activate' => true
]);

View file

@ -30,7 +30,7 @@ trait StorageBase
'name' => 'Test Bucket',
'fileSecurity' => true,
'maximumFileSize' => 2000000, //2MB
'allowedFileExtensions' => ['jpg', 'png', 'jfif'],
'allowedFileExtensions' => ['jpg', 'png', 'jfif', 'webp'],
'permissions' => [
Permission::read(Role::any()),
Permission::create(Role::any()),
@ -263,7 +263,39 @@ trait StorageBase
$this->assertEquals(400, $res['headers']['status-code']);
$this->assertEquals(Exception::STORAGE_INVALID_APPWRITE_ID, $res['body']['type']);
return ['bucketId' => $bucketId, 'fileId' => $file['body']['$id'], 'largeFileId' => $largeFile['body']['$id'], 'largeBucketId' => $bucket2['body']['$id']];
/**
* Test for SUCCESS - Upload and view webp image
*/
$webpFile = $this->client->call(Client::METHOD_POST, '/storage/buckets/' . $bucketId . '/files', array_merge([
'content-type' => 'multipart/form-data',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'fileId' => ID::unique(),
'file' => new CURLFile(realpath(__DIR__ . '/../../../resources/image.webp'), 'image/webp', 'image.webp'),
'permissions' => [
Permission::read(Role::any()),
Permission::update(Role::any()),
Permission::delete(Role::any()),
],
]);
$this->assertEquals(201, $webpFile['headers']['status-code']);
$this->assertNotEmpty($webpFile['body']['$id']);
$this->assertEquals('image.webp', $webpFile['body']['name']);
$this->assertEquals('image/webp', $webpFile['body']['mimeType']);
$webpFileId = $webpFile['body']['$id'];
// View webp file
$webpView = $this->client->call(Client::METHOD_GET, '/storage/buckets/' . $bucketId . '/files/' . $webpFileId . '/view', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$this->assertEquals(200, $webpView['headers']['status-code']);
$this->assertEquals('image/webp', $webpView['headers']['content-type']);
$this->assertNotEmpty($webpView['body']);
return ['bucketId' => $bucketId, 'fileId' => $file['body']['$id'], 'largeFileId' => $largeFile['body']['$id'], 'largeBucketId' => $bucket2['body']['$id'], 'webpFileId' => $webpFileId];
}
public function testCreateBucketFileZstdCompression(): array
@ -416,7 +448,7 @@ trait StorageBase
],
]);
$this->assertEquals(200, $files['headers']['status-code']);
$this->assertEquals(0, count($files['body']['files']));
$this->assertEquals(1, count($files['body']['files']));
$files = $this->client->call(Client::METHOD_GET, '/storage/buckets/' . $data['bucketId'] . '/files', array_merge([
'content-type' => 'application/json',
@ -869,6 +901,31 @@ trait StorageBase
return $data;
}
/**
* @depends testCreateBucketFile
*/
public function testFilePreview(array $data): array
{
$bucketId = $data['bucketId'];
$fileId = $data['fileId'];
// Preview PNG as webp
$preview = $this->client->call(Client::METHOD_GET, '/storage/buckets/' . $bucketId . '/files/' . $fileId . '/preview', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'width' => 300,
'height' => 300,
'output' => 'webp',
]);
$this->assertEquals(200, $preview['headers']['status-code']);
$this->assertEquals('image/webp', $preview['headers']['content-type']);
$this->assertNotEmpty($preview['body']);
return $data;
}
/**
* @depends testUpdateBucketFile
*/

View file

@ -9,7 +9,6 @@ use Tests\E2E\Client;
use Tests\E2E\Scopes\ProjectCustom;
use Tests\E2E\Scopes\Scope;
use Tests\E2E\Scopes\SideServer;
use Utopia\Database\DateTime;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
@ -63,10 +62,23 @@ class TokensConsoleClientTest extends Scope
$fileId = $file['body']['$id'];
// Failure case: Expire date is in the past
$token = $this->client->call(Client::METHOD_POST, '/tokens/buckets/' . $bucketId . '/files/' . $fileId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id']
], $this->getHeaders()));
], $this->getHeaders()), [
'expire' => '2022-11-02',
]);
$this->assertEquals(400, $token['headers']['status-code']);
$this->assertStringContainsString('Value must be valid date in the future', $token['body']['message']);
// Success case: No expire date
$token = $this->client->call(Client::METHOD_POST, '/tokens/buckets/' . $bucketId . '/files/' . $fileId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id']
], $this->getHeaders()), [
'expire' => null,
]);
$this->assertEquals(201, $token['headers']['status-code']);
$this->assertEquals('files', $token['body']['resourceType']);
@ -107,8 +119,19 @@ class TokensConsoleClientTest extends Scope
{
$tokenId = $data['tokenId'];
// Failure case: Expire date is in the past
$token = $this->client->call(Client::METHOD_PATCH, '/tokens/' . $tokenId, [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
], [
'expire' => '2022-11-02',
]);
$this->assertEquals(400, $token['headers']['status-code']);
$this->assertStringContainsString('Value must be valid date in the future', $token['body']['message']);
// Finite expiry
$expiry = DateTime::addSeconds(new \DateTime(), 3600);
$expiry = date('Y-m-d', strtotime("tomorrow"));
$token = $this->client->call(Client::METHOD_PATCH, '/tokens/' . $tokenId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id']

View file

@ -7,7 +7,6 @@ use Tests\E2E\Client;
use Tests\E2E\Scopes\ProjectCustom;
use Tests\E2E\Scopes\Scope;
use Tests\E2E\Scopes\SideServer;
use Utopia\Database\DateTime;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
@ -61,6 +60,17 @@ class TokensCustomServerTest extends Scope
$fileId = $file['body']['$id'];
// Failure case: Expire date is in the past
$token = $this->client->call(Client::METHOD_POST, '/tokens/buckets/' . $bucketId . '/files/' . $fileId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id']
], $this->getHeaders()), [
'expire' => '2022-11-02',
]);
$this->assertEquals(400, $token['headers']['status-code']);
$this->assertStringContainsString('Value must be valid date in the future', $token['body']['message']);
// Success case: No expire date
$token = $this->client->call(Client::METHOD_POST, '/tokens/buckets/' . $bucketId . '/files/' . $fileId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id']
@ -83,8 +93,19 @@ class TokensCustomServerTest extends Scope
{
$tokenId = $data['tokenId'];
// Finite expiry
$expiry = DateTime::addSeconds(new \DateTime(), 3600);
// Failure case: Expire date is in the past
$token = $this->client->call(Client::METHOD_PATCH, '/tokens/' . $tokenId, [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
], [
'expire' => '2022-11-02',
]);
$this->assertEquals(400, $token['headers']['status-code']);
$this->assertStringContainsString('Value must be valid date in the future', $token['body']['message']);
// Success case: Finite expiry
$expiry = date('Y-m-d', strtotime("tomorrow"));
$token = $this->client->call(Client::METHOD_PATCH, '/tokens/' . $tokenId, [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
@ -94,9 +115,10 @@ class TokensCustomServerTest extends Scope
]);
$dateValidator = new DatetimeValidator();
$this->assertEquals(200, $token['headers']['status-code']);
$this->assertTrue($dateValidator->isValid($token['body']['expire']));
// Infinite expiry
// Success case: Infinite expiry
$token = $this->client->call(Client::METHOD_PATCH, '/tokens/' . $tokenId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],

BIN
tests/resources/image.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 634 B