Merge branch 'feat-sites' into feat-all-rules-in-proxy

This commit is contained in:
Matej Bačo
2025-02-17 09:47:33 +01:00
23 changed files with 1253 additions and 239 deletions
+16
View File
@@ -0,0 +1,16 @@
name: "Static code analysis"
on: [pull_request]
jobs:
lint:
name: CodeQL
runs-on: ubuntu-latest
steps:
- name: Check out the repo
uses: actions/checkout@v2
- name: Run CodeQL
run: |
docker run --rm -v $PWD:/app composer:2.6 sh -c \
"composer install --profile --ignore-platform-reqs && composer check"
+219 -65
View File
@@ -1,9 +1,21 @@
<?php
use Utopia\System\System;
/**
* List of Appwrite Sites templates
*/
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https';
$hostname = System::getEnv('_APP_DOMAIN');
// TODO: Development override
if (System::getEnv('_APP_ENV') === 'development') {
$hostname = 'localhost';
}
$url = $protocol . '://' . $hostname;
// TODO: @Meldiron Angular
const TEMPLATE_FRAMEWORKS = [
@@ -77,62 +89,10 @@ function getFramework(string $frameworkEnum, array $overrides)
return [
[
'key' => 'nextjs-starter',
'name' => 'Next.js starter website',
'key' => 'starter-for-svelte',
'name' => 'Svelte starter',
'useCases' => ['starter'],
'demoUrl' => 'https://nextjs-starter.sites.qa17.appwrite.org/',
'demoImage' => 'https://qa17.appwrite.org/console/images/sites/templates/nextjs-starter.png',
'frameworks' => [
getFramework('NEXTJS', [
'providerRootDirectory' => './nextjs/starter',
]),
],
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.2.*',
'variables' => [],
],
[
'key' => 'nuxt-starter',
'name' => 'Nuxt starter website',
'useCases' => ['starter'],
'demoUrl' => 'https://nuxt-starter.sites.qa17.appwrite.org/',
'demoImage' => 'https://qa17.appwrite.org/console/images/sites/templates/nuxt-starter.png',
'frameworks' => [
getFramework('NUXT', [
'providerRootDirectory' => './nuxt/starter',
]),
],
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.2.*',
'variables' => [],
],
[
'key' => 'sveltekit-starter',
'name' => 'SvelteKit starter website',
'useCases' => ['starter'],
'demoUrl' => 'https://sveltekit-starter.sites.qa17.appwrite.org/',
'demoImage' => 'https://qa17.appwrite.org/console/images/sites/templates/sveltekit-starter.png',
'frameworks' => [
getFramework('SVELTEKIT', [
'providerRootDirectory' => './sveltekit/starter',
]),
],
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.2.*',
'variables' => [],
],
[
'key' => 'sveltekit-with-appwrite',
'name' => 'SvelteKit with Appwrite',
'useCases' => ['starter'],
'demoUrl' => '',
'demoImage' => '',
'demoImage' => $url . '/console/images/sites/templates/starter-for-svelte.png',
'frameworks' => [
getFramework('SVELTEKIT', [
'providerRootDirectory' => './',
@@ -147,7 +107,7 @@ return [
'name' => 'PUBLIC_APPWRITE_ENDPOINT',
'description' => 'Endpoint of Appwrite server',
'value' => '{apiEndpoint}',
'placeholder' => 'https://cloud.appwrite.io/v1',
'placeholder' => '{apiEndpoint}',
'required' => true,
'type' => 'text'
],
@@ -167,14 +127,210 @@ return [
'required' => true,
'type' => 'text'
],
]
],
[
'key' => 'starter-for-nextjs',
'name' => 'Next.js starter',
'useCases' => ['starter'],
'demoImage' => $url . '/console/images/sites/templates/starter-for-nextjs.png',
'frameworks' => [
getFramework('NEXTJS', [
'providerRootDirectory' => './',
]),
],
'vcsProvider' => 'github',
'providerRepositoryId' => 'starter-for-nextjs',
'providerOwner' => 'appwrite',
'providerVersion' => '0.1.*',
'variables' => [
[
'name' => 'NEXT_PUBLIC_APPWRITE_ENDPOINT',
'description' => 'Endpoint of Appwrite server',
'value' => '{apiEndpoint}',
'placeholder' => '{apiEndpoint}',
'required' => true,
'type' => 'text'
],
[
'name' => 'NEXT_PUBLIC_APPWRITE_PROJECT_ID',
'description' => 'Your Appwrite project ID',
'value' => '{projectId}',
'placeholder' => '{projectId}',
'required' => true,
'type' => 'text'
],
[
'name' => 'NEXT_PUBLIC_APPWRITE_PROJECT_NAME',
'description' => 'Your Appwrite project name',
'value' => '{projectName}',
'placeholder' => '{projectName}',
'required' => true,
'type' => 'text'
],
]
],
[
'key' => 'template-for-event',
'name' => 'Event template',
'useCases' => ['starter'],
'demoImage' => $url . '/console/images/sites/templates/template-for-event.png',
'frameworks' => [
getFramework('NEXTJS', [
'providerRootDirectory' => './',
'installCommand' => 'pnpm install',
'buildCommand' => 'npm run build',
]),
],
'vcsProvider' => 'github',
'providerRepositoryId' => 'template-for-event',
'providerOwner' => 'appwrite',
'providerVersion' => '0.1.*',
'variables' => [
[
'name' => 'NEXT_PUBLIC_APPWRITE_FUNCTION_PROJECT_ID',
'description' => 'Endpoint of Appwrite server',
'value' => '{apiEndpoint}',
'placeholder' => '{apiEndpoint}',
'required' => true,
'type' => 'text'
],
[
'name' => 'NEXT_PUBLIC_APPWRITE_FUNCTION_API_ENDPOINT',
'description' => 'Your Appwrite project ID',
'value' => '{projectId}',
'placeholder' => '{projectId}',
'required' => true,
'type' => 'text'
],
]
],
[
'key' => 'template-for-portfolio',
'name' => 'Portfolio template',
'useCases' => ['starter'],
'demoImage' => $url . '/console/images/sites/templates/template-for-portfolio.png',
'frameworks' => [
getFramework('NEXTJS', [
'providerRootDirectory' => './',
]),
],
'vcsProvider' => 'github',
'providerRepositoryId' => 'template-for-portfolio',
'providerOwner' => 'appwrite',
'providerVersion' => '0.1.*',
'variables' => []
],
[
'key' => 'template-for-store',
'name' => 'Store template',
'useCases' => ['starter'],
'demoImage' => $url . '/console/images/sites/templates/template-for-store.png',
'frameworks' => [
getFramework('SVELTEKIT', [
'providerRootDirectory' => './',
]),
],
'vcsProvider' => 'github',
'providerRepositoryId' => 'template-for-store',
'providerOwner' => 'appwrite',
'providerVersion' => '0.1.*',
'variables' => [
[
'name' => 'STRIPE_SECRET_KEY',
'description' => 'Your Stripe secret key',
'value' => 'disabled',
'placeholder' => 'sk_.....',
'required' => false,
'type' => 'password'
],
[
'name' => 'PUBLIC_APPWRITE_ENDPOINT',
'description' => 'Endpoint of Appwrite server',
'value' => '{apiEndpoint}',
'placeholder' => '{apiEndpoint}',
'required' => true,
'type' => 'text'
],
[
'name' => 'PUBLIC_APPWRITE_PROJECT_ID',
'description' => 'Your Appwrite project ID',
'value' => '{projectId}',
'placeholder' => '{projectId}',
'required' => true,
'type' => 'text'
],
]
],
[
'key' => 'template-for-blog',
'name' => 'Blog template',
'useCases' => ['starter'],
'demoImage' => $url . '/console/images/sites/templates/template-for-blog.png',
'frameworks' => [
getFramework('SVELTEKIT', [
'providerRootDirectory' => './',
]),
],
'vcsProvider' => 'github',
'providerRepositoryId' => 'template-for-blog',
'providerOwner' => 'appwrite',
'providerVersion' => '0.1.*',
'variables' => []
],
[
'key' => 'nextjs-starter',
'name' => 'Next.js starter',
'useCases' => ['starter'],
'demoImage' => '',
'frameworks' => [
getFramework('NEXTJS', [
'providerRootDirectory' => './nextjs/starter',
]),
],
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.2.*',
'variables' => [],
],
[
'key' => 'nuxt-starter',
'name' => 'Nuxt starter',
'useCases' => ['starter'],
'demoImage' => '',
'frameworks' => [
getFramework('NUXT', [
'providerRootDirectory' => './nuxt/starter',
]),
],
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.2.*',
'variables' => [],
],
[
'key' => 'sveltekit-starter',
'name' => 'SvelteKit starter',
'useCases' => ['starter'],
'demoImage' => '',
'frameworks' => [
getFramework('SVELTEKIT', [
'providerRootDirectory' => './sveltekit/starter',
]),
],
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.2.*',
'variables' => [],
],
[
'key' => 'astro-starter',
'name' => 'Astro starter website',
'name' => 'Astro starter',
'useCases' => ['starter'],
'demoUrl' => 'https://astro-starter.sites.qa17.appwrite.org/',
'demoImage' => 'https://qa17.appwrite.org/console/images/sites/templates/astro-starter.png',
'demoImage' => '',
'frameworks' => [
getFramework('ASTRO', [
'providerRootDirectory' => './astro/starter',
@@ -188,10 +344,9 @@ return [
],
[
'key' => 'remix-starter',
'name' => 'Remix starter website',
'name' => 'Remix starter',
'useCases' => ['starter'],
'demoUrl' => 'https://remix-starter.sites.qa17.appwrite.org/',
'demoImage' => 'https://qa17.appwrite.org/console/images/sites/templates/remix-starter.png',
'demoImage' => '',
'frameworks' => [
getFramework('REMIX', [
'providerRootDirectory' => './remix/starter',
@@ -205,10 +360,9 @@ return [
],
[
'key' => 'flutter-starter',
'name' => 'Flutter starter website',
'name' => 'Flutter starter',
'useCases' => ['starter'],
'demoUrl' => 'https://flutter-starter.sites.qa17.appwrite.org/',
'demoImage' => 'https://qa17.appwrite.org/console/images/sites/templates/flutter-starter.png',
'demoImage' => '',
'frameworks' => [
getFramework('FLUTTER', [
'providerRootDirectory' => './flutter/starter',
+36 -13
View File
@@ -15,6 +15,8 @@ use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Transformation\Adapter\Preview;
use Appwrite\Transformation\Transformation;
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Request\Filters\V16 as RequestV16;
use Appwrite\Utopia\Request\Filters\V17 as RequestV17;
@@ -123,14 +125,14 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
$type = $rule->getAttribute('resourceType');
if ($type === 'function' || $type === 'site' || $type === 'deployment') {
$resourceCollection = match($type) {
$resourceCollection = match ($type) {
'function' => 'functions',
'site' => 'sites',
'deployment' => 'deployments',
};
}
if ($type === 'function' || $type === 'site' || $type === 'deployment') {
if ($type === 'function' || $type === 'site' || $type === 'deployment') {
$method = $utopia->getRoute()?->getLabel('sdk', null);
if (empty($method)) {
@@ -198,7 +200,7 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
throw new AppwriteException(AppwriteException::GENERAL_RESOURCE_BLOCKED);
}
$version = match($type) {
$version = match ($type) {
'function' => $resource->getAttribute('version', 'v2'),
'site' => 'v4',
'deployment' => 'v4'
@@ -207,7 +209,7 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
$runtimes = Config::getParam($version === 'v2' ? 'runtimes-v2' : 'runtimes', []);
$spec = Config::getParam('runtime-specifications')[$resource->getAttribute('specification', APP_COMPUTE_SPECIFICATION_DEFAULT)];
$runtime = match($type) {
$runtime = match ($type) {
'function' => $runtimes[$resource->getAttribute('runtime')] ?? null,
'site' => $runtimes[$resource->getAttribute('buildRuntime')] ?? null,
'deployment' => $runtimes[$resource->getAttribute('buildRuntime')] ?? null,
@@ -222,7 +224,7 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
throw new AppwriteException(AppwriteException::FUNCTION_RUNTIME_UNSUPPORTED, 'Runtime "' . $resource->getAttribute('runtime', '') . '" is not supported');
}
$deploymentId = match($type) {
$deploymentId = match ($type) {
'function' => $resource->getAttribute('deployment', ''),
'site' => $resource->getAttribute('deploymentId', ''),
'deployment' => $subResource->getId()
@@ -388,12 +390,12 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
/** Execute function */
$executor = new Executor(System::getEnv('_APP_EXECUTOR_HOST'));
try {
$version = match($type) {
$version = match ($type) {
'function' => $resource->getAttribute('version', 'v2'),
'site' => 'v4',
'deployment' => 'v4'
};
$entrypoint = match($type) {
$entrypoint = match ($type) {
'function' => $deployment->getAttribute('entrypoint', ''),
'site' => '',
'deployment' => ''
@@ -420,7 +422,7 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
$runtimeEntrypoint = 'cp /tmp/code.tar.gz /mnt/code/code.tar.gz && nohup helpers/start.sh "' . $startCommand . '"';
}
$entrypoint = match($type) {
$entrypoint = match ($type) {
'function' => $deployment->getAttribute('entrypoint', ''),
'site' => '',
'deployment' => ''
@@ -446,6 +448,22 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
requestTimeout: 30
);
// Branded banner for previews
$transformation = new Transformation();
$transformation->addAdapter(new Preview());
$transformation->setInput($executionResponse['body']);
$transformation->setTraits($executionResponse['headers']);
if ($type === 'deployment' && $transformation->transform()) {
$executionResponse['body'] = $transformation->getOutput();
foreach ($executionResponse['headers'] as $key => $value) {
if (\strtolower($key) === 'content-length') {
$executionResponse['headers'][$key] = \strlen($executionResponse['body']);
}
}
}
$headersFiltered = [];
foreach ($executionResponse['headers'] as $key => $value) {
if (\in_array(\strtolower($key), FUNCTION_ALLOWLIST_HEADERS_RESPONSE)) {
@@ -472,8 +490,8 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
if ($type === 'function') {
$execution
->setAttribute('status', 'failed')
->setAttribute('errors', $th->getMessage() . '\nError Code: ' . $th->getCode());
->setAttribute('status', 'failed')
->setAttribute('errors', $th->getMessage() . '\nError Code: ' . $th->getCode());
}
Console::error($th->getMessage());
@@ -540,8 +558,7 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
->addMetric(METRIC_EXECUTIONS_MB_SECONDS, (int)(($spec['memory'] ?? APP_COMPUTE_MEMORY_DEFAULT) * $execution->getAttribute('duration', 0) * ($spec['cpus'] ?? APP_COMPUTE_CPUS_DEFAULT)))
->addMetric(str_replace('{functionInternalId}', $resource->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS_MB_SECONDS), (int)(($spec['memory'] ?? APP_COMPUTE_MEMORY_DEFAULT) * $execution->getAttribute('duration', 0) * ($spec['cpus'] ?? APP_COMPUTE_CPUS_DEFAULT)))
->setProject($project)
->trigger()
;
->trigger();
return true;
} elseif ($type === 'api') {
@@ -720,6 +737,12 @@ App::init()
$validator = new Hostname($clients);
if ($validator->isValid($origin)) {
$refDomainOrigin = $origin;
} else {
// Auto-allow domains with linked rule
$rule = Authorization::skip(fn () => $dbForPlatform->getDocument('rules', md5($origin)));
if (!$rule->isEmpty() && $rule->getAttribute('projectInternalId') === $project->getInternalId()) {
$refDomainOrigin = $origin;
}
}
$refDomain = (!empty($protocol) ? $protocol : $request->getProtocol()) . '://' . $refDomainOrigin . (!empty($port) ? ':' . $port : '');
@@ -769,7 +792,7 @@ App::init()
$response->addFilter(new ResponseV18());
}
if (version_compare($responseFormat, APP_VERSION_STABLE, '>')) {
$response->addHeader('X-Appwrite-Warning', "The current SDK is built for Appwrite " . $responseFormat . ". However, the current Appwrite server version is ". APP_VERSION_STABLE . ". Please downgrade your SDK to match the Appwrite version: https://appwrite.io/docs/sdks");
$response->addHeader('X-Appwrite-Warning', "The current SDK is built for Appwrite " . $responseFormat . ". However, the current Appwrite server version is " . APP_VERSION_STABLE . ". Please downgrade your SDK to match the Appwrite version: https://appwrite.io/docs/sdks");
}
}
+4 -2
View File
@@ -14,7 +14,8 @@
"test": "vendor/bin/phpunit",
"lint": "vendor/bin/pint --test",
"format": "vendor/bin/pint",
"bench": "vendor/bin/phpbench run --report=benchmark"
"bench": "vendor/bin/phpbench run --report=benchmark",
"check": "./vendor/bin/phpstan analyse -c phpstan.neon"
},
"autoload": {
"psr-4": {
@@ -89,7 +90,8 @@
"swoole/ide-helper": "5.1.2",
"textalk/websocket": "1.5.7",
"laravel/pint": "^1.14",
"phpbench/phpbench": "^1.2"
"phpbench/phpbench": "^1.2",
"phpstan/phpstan": "1.8.*"
},
"provide": {
"ext-phpiredis": "*"
Generated
+73 -14
View File
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "4da9bee4423753c2e592c081b19b8711",
"content-hash": "5473f6b65d54314228e69b6bed489d78",
"packages": [
{
"name": "adhocore/jwt",
@@ -5560,16 +5560,16 @@
},
{
"name": "myclabs/deep-copy",
"version": "1.12.1",
"version": "1.13.0",
"source": {
"type": "git",
"url": "https://github.com/myclabs/DeepCopy.git",
"reference": "123267b2c49fbf30d78a7b2d333f6be754b94845"
"reference": "024473a478be9df5fdaca2c793f2232fe788e414"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/123267b2c49fbf30d78a7b2d333f6be754b94845",
"reference": "123267b2c49fbf30d78a7b2d333f6be754b94845",
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/024473a478be9df5fdaca2c793f2232fe788e414",
"reference": "024473a478be9df5fdaca2c793f2232fe788e414",
"shasum": ""
},
"require": {
@@ -5608,7 +5608,7 @@
],
"support": {
"issues": "https://github.com/myclabs/DeepCopy/issues",
"source": "https://github.com/myclabs/DeepCopy/tree/1.12.1"
"source": "https://github.com/myclabs/DeepCopy/tree/1.13.0"
},
"funding": [
{
@@ -5616,7 +5616,7 @@
"type": "tidelift"
}
],
"time": "2024-11-08T17:47:46+00:00"
"time": "2025-02-12T12:17:51+00:00"
},
{
"name": "nikic/php-parser",
@@ -6190,16 +6190,16 @@
},
{
"name": "phpstan/phpdoc-parser",
"version": "2.0.0",
"version": "2.0.1",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpdoc-parser.git",
"reference": "c00d78fb6b29658347f9d37ebe104bffadf36299"
"reference": "72e51f7c32c5aef7c8b462195b8c599b11199893"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/c00d78fb6b29658347f9d37ebe104bffadf36299",
"reference": "c00d78fb6b29658347f9d37ebe104bffadf36299",
"url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/72e51f7c32c5aef7c8b462195b8c599b11199893",
"reference": "72e51f7c32c5aef7c8b462195b8c599b11199893",
"shasum": ""
},
"require": {
@@ -6231,9 +6231,68 @@
"description": "PHPDoc parser with support for nullable, intersection and generic types",
"support": {
"issues": "https://github.com/phpstan/phpdoc-parser/issues",
"source": "https://github.com/phpstan/phpdoc-parser/tree/2.0.0"
"source": "https://github.com/phpstan/phpdoc-parser/tree/2.0.1"
},
"time": "2024-10-13T11:29:49+00:00"
"time": "2025-02-13T12:25:43+00:00"
},
{
"name": "phpstan/phpstan",
"version": "1.8.11",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan.git",
"reference": "46e223dd68a620da18855c23046ddb00940b4014"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/46e223dd68a620da18855c23046ddb00940b4014",
"reference": "46e223dd68a620da18855c23046ddb00940b4014",
"shasum": ""
},
"require": {
"php": "^7.2|^8.0"
},
"conflict": {
"phpstan/phpstan-shim": "*"
},
"bin": [
"phpstan",
"phpstan.phar"
],
"type": "library",
"autoload": {
"files": [
"bootstrap.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "PHPStan - PHP Static Analysis Tool",
"keywords": [
"dev",
"static analysis"
],
"support": {
"issues": "https://github.com/phpstan/phpstan/issues",
"source": "https://github.com/phpstan/phpstan/tree/1.8.11"
},
"funding": [
{
"url": "https://github.com/ondrejmirtes",
"type": "github"
},
{
"url": "https://github.com/phpstan",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan",
"type": "tidelift"
}
],
"time": "2022-10-24T15:45:13+00:00"
},
{
"name": "phpunit/php-code-coverage",
@@ -8780,5 +8839,5 @@
"platform-overrides": {
"php": "8.3"
},
"plugin-api-version": "2.6.0"
"plugin-api-version": "2.3.0"
}
+1 -1
View File
@@ -204,7 +204,7 @@ services:
appwrite-console:
<<: *x-logging
container_name: appwrite-console
image: appwrite/console:5.3.0-sites-rc.6
image: appwrite/console:5.3.0-sites-rc.9
restart: unless-stopped
networks:
- appwrite
+8
View File
@@ -0,0 +1,8 @@
parameters:
level: 8
paths:
- src/Appwrite/Transformation
scanDirectories:
- vendor/swoole/ide-helper
excludePaths:
- tests/resources
@@ -19,7 +19,7 @@ use Utopia\VCS\Exception\RepositoryNotFound;
class Base extends Action
{
public function redeployVcsFunction(Request $request, Document $function, Document $project, Document $installation, Database $dbForProject, Build $queueForBuilds, Document $template, GitHub $github)
public function redeployVcsFunction(Request $request, Document $function, Document $project, Document $installation, Database $dbForProject, Build $queueForBuilds, Document $template, GitHub $github): Document
{
$deploymentId = ID::unique();
$entrypoint = $function->getAttribute('entrypoint', '');
@@ -91,9 +91,11 @@ class Base extends Action
->setResource($function)
->setDeployment($deployment)
->setTemplate($template);
return $deployment;
}
public function redeployVcsSite(Request $request, Document $site, Document $project, Document $installation, Database $dbForProject, Database $dbForPlatform, Build $queueForBuilds, Document $template, GitHub $github)
public function redeployVcsSite(Request $request, Document $site, Document $project, Document $installation, Database $dbForProject, Database $dbForPlatform, Build $queueForBuilds, Document $template, GitHub $github): Document
{
$deploymentId = ID::unique();
$providerInstallationId = $installation->getAttribute('providerInstallationId', '');
@@ -187,5 +189,7 @@ class Base extends Action
->setResource($site)
->setDeployment($deployment)
->setTemplate($template);
return $deployment;
}
}
@@ -0,0 +1,143 @@
<?php
namespace Appwrite\Platform\Modules\Functions\Http\Deployments\Template;
use Appwrite\Event\Build;
use Appwrite\Event\Event;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Swoole\Request;
use Utopia\Validator\Boolean;
use Utopia\Validator\Text;
use Utopia\VCS\Adapter\Git\GitHub;
class Create extends Base
{
use HTTP;
public static function getName()
{
return 'createTemplateDeployment';
}
public function __construct()
{
$this
->setHttpMethod(Action::HTTP_REQUEST_METHOD_POST)
->setHttpPath('/v1/functions/:functionId/deployments/template')
->desc('Create template deployment')
->groups(['api', 'functions'])
->label('scope', 'functions.write')
->label('event', 'functions.[functionId].deployments.[deploymentId].create')
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
->label('audits.event', 'deployment.create')
->label('audits.resource', 'function/{request.functionId}')
->label('sdk', new Method(
namespace: 'functions',
name: 'createTemplateDeployment',
description: <<<EOT
Create a deployment based on a template.
Use this endpoint with combination of [listTemplates](https://appwrite.io/docs/server/functions#listTemplates) to find the template details.
EOT,
auth: [AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_ACCEPTED,
model: Response::MODEL_DEPLOYMENT,
)
],
))
->param('functionId', '', new UID(), 'Function ID.')
->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('activate', false, new Boolean(), 'Automatically activate the deployment when it is finished building.', true)
->inject('request')
->inject('response')
->inject('dbForProject')
->inject('dbForPlatform')
->inject('queueForEvents')
->inject('project')
->inject('queueForBuilds')
->inject('gitHub')
->callback([$this, 'action']);
}
public function action(string $functionId, string $repository, string $owner, string $rootDirectory, string $version, bool $activate, Request $request, Response $response, Database $dbForProject, Database $dbForPlatform, Event $queueForEvents, Document $project, Build $queueForBuilds, GitHub $github)
{
$function = $dbForProject->getDocument('functions', $functionId);
if ($function->isEmpty()) {
throw new Exception(Exception::FUNCTION_NOT_FOUND);
}
$template = new Document([
'repositoryName' => $repository,
'ownerName' => $owner,
'rootDirectory' => $rootDirectory,
'version' => $version
]);
if (!empty($function->getAttribute('providerRepositoryId'))) {
$installation = $dbForPlatform->getDocument('installations', $function->getAttribute('installationId'));
$deployment = $this->redeployVcsFunction($request, $function, $project, $installation, $dbForProject, $queueForBuilds, $template, $github);
$queueForEvents
->setParam('functionId', $function->getId())
->setParam('deploymentId', $deployment->getId());
$response
->setStatusCode(Response::STATUS_CODE_ACCEPTED)
->dynamic($deployment, Response::MODEL_DEPLOYMENT);
return;
}
$deploymentId = ID::unique();
$deployment = $dbForProject->createDocument('deployments', new Document([
'$id' => $deploymentId,
'$permissions' => [
Permission::read(Role::any()),
Permission::update(Role::any()),
Permission::delete(Role::any()),
],
'resourceId' => $function->getId(),
'resourceInternalId' => $function->getInternalId(),
'resourceType' => 'functions',
'entrypoint' => $function->getAttribute('entrypoint', ''),
'commands' => $function->getAttribute('commands', ''),
'type' => 'manual',
'search' => implode(' ', [$deploymentId, $function->getAttribute('entrypoint', '')]),
'activate' => $activate,
]));
$queueForBuilds
->setType(BUILD_TYPE_DEPLOYMENT)
->setResource($function)
->setDeployment($deployment)
->setTemplate($template);
$queueForEvents
->setParam('functionId', $function->getId())
->setParam('deploymentId', $deployment->getId());
$response
->setStatusCode(Response::STATUS_CODE_ACCEPTED)
->dynamic($deployment, Response::MODEL_DEPLOYMENT);
}
}
@@ -2,7 +2,6 @@
namespace Appwrite\Platform\Modules\Functions\Http\Functions;
use Appwrite\Event\Build;
use Appwrite\Event\Event;
use Appwrite\Event\Validator\FunctionEvent;
use Appwrite\Extend\Exception;
@@ -27,14 +26,12 @@ use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Roles;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Swoole\Request;
use Utopia\System\System;
use Utopia\Validator\ArrayList;
use Utopia\Validator\Boolean;
use Utopia\Validator\Range;
use Utopia\Validator\Text;
use Utopia\Validator\WhiteList;
use Utopia\VCS\Adapter\Git\GitHub;
class Create extends Base
{
@@ -88,30 +85,22 @@ class Create extends Base
->param('providerBranch', '', new Text(128, 0), 'Production branch for the repo linked to the function.', true)
->param('providerSilentMode', false, new Boolean(), 'Is the VCS (Version Control System) connection in silent mode for the repo linked to the function? In silent mode, comments will not be made on commits and pull requests.', true)
->param('providerRootDirectory', '', new Text(128, 0), 'Path to function code in the linked repo.', true)
->param('templateRepository', '', new Text(128, 0), 'Repository name of the template.', true)
->param('templateOwner', '', new Text(128, 0), 'The name of the owner of the template.', true)
->param('templateRootDirectory', '', new Text(128, 0), 'Path to function code in the template repo.', true)
->param('templateVersion', '', new Text(128, 0), 'Version (tag) for the repo linked to the function template.', true)
->param('specification', APP_COMPUTE_SPECIFICATION_DEFAULT, fn (array $plan) => new RuntimeSpecification(
$plan,
Config::getParam('runtime-specifications', []),
App::getEnv('_APP_COMPUTE_CPUS', APP_COMPUTE_CPUS_DEFAULT),
App::getEnv('_APP_COMPUTE_MEMORY', APP_COMPUTE_MEMORY_DEFAULT)
), 'Runtime specification for the function and builds.', true, ['plan'])
->inject('request')
->inject('response')
->inject('dbForProject')
->inject('timelimit')
->inject('project')
->inject('user')
->inject('queueForEvents')
->inject('queueForBuilds')
->inject('dbForPlatform')
->inject('gitHub')
->callback([$this, 'action']);
}
public function action(string $functionId, string $name, string $runtime, array $execute, array $events, string $schedule, int $timeout, bool $enabled, bool $logging, string $entrypoint, string $commands, array $scopes, string $installationId, string $providerRepositoryId, string $providerBranch, bool $providerSilentMode, string $providerRootDirectory, string $templateRepository, string $templateOwner, string $templateRootDirectory, string $templateVersion, string $specification, Request $request, Response $response, Database $dbForProject, callable $timelimit, Document $project, Document $user, Event $queueForEvents, Build $queueForBuilds, Database $dbForPlatform, GitHub $github)
public function action(string $functionId, string $name, string $runtime, array $execute, array $events, string $schedule, int $timeout, bool $enabled, bool $logging, string $entrypoint, string $commands, array $scopes, string $installationId, string $providerRepositoryId, string $providerBranch, bool $providerSilentMode, string $providerRootDirectory, string $specification, Response $response, Database $dbForProject, callable $timelimit, Document $project, Event $queueForEvents, Database $dbForPlatform)
{
// Temporary abuse check
@@ -151,20 +140,6 @@ class Create extends Base
throw new Exception(Exception::FUNCTION_RUNTIME_UNSUPPORTED, 'Runtime "' . $runtime . '" is not supported');
}
// build from template
$template = new Document([]);
if (
!empty($templateRepository)
&& !empty($templateOwner)
&& !empty($templateRootDirectory)
&& !empty($templateVersion)
) {
$template->setAttribute('repositoryName', $templateRepository)
->setAttribute('ownerName', $templateOwner)
->setAttribute('rootDirectory', $templateRootDirectory)
->setAttribute('version', $templateVersion);
}
$installation = $dbForPlatform->getDocument('installations', $installationId);
if (!empty($installationId) && $installation->isEmpty()) {
@@ -252,36 +227,6 @@ class Create extends Base
$function = $dbForProject->updateDocument('functions', $function->getId(), $function);
if (!empty($providerRepositoryId)) {
// Deploy VCS
$this->redeployVcsFunction($request, $function, $project, $installation, $dbForProject, $queueForBuilds, $template, $github);
} elseif (!$template->isEmpty()) {
// Deploy non-VCS from template
$deploymentId = ID::unique();
$deployment = $dbForProject->createDocument('deployments', new Document([
'$id' => $deploymentId,
'$permissions' => [
Permission::read(Role::any()),
Permission::update(Role::any()),
Permission::delete(Role::any()),
],
'resourceId' => $function->getId(),
'resourceInternalId' => $function->getInternalId(),
'resourceType' => 'functions',
'entrypoint' => $function->getAttribute('entrypoint', ''),
'commands' => $function->getAttribute('commands', ''),
'type' => 'manual',
'search' => implode(' ', [$deploymentId, $function->getAttribute('entrypoint', '')]),
'activate' => true,
]));
$queueForBuilds
->setType(BUILD_TYPE_DEPLOYMENT)
->setResource($function)
->setDeployment($deployment)
->setTemplate($template);
}
$queueForEvents->setParam('functionId', $function->getId());
$response
@@ -3,6 +3,7 @@
namespace Appwrite\Platform\Modules\Functions\Services;
use Appwrite\Platform\Modules\Functions\Http\Deployments\Create as CreateDeployment;
use Appwrite\Platform\Modules\Functions\Http\Deployments\Template\Create as CreateTemplateDeployment;
use Appwrite\Platform\Modules\Functions\Http\Functions\Create as CreateFunction;
use Appwrite\Platform\Modules\Functions\Http\Functions\Update as UpdateFunction;
use Appwrite\Platform\Modules\Functions\Http\Functions\XList as ListFunctions;
@@ -19,5 +20,6 @@ class Http extends Service
$this->addAction(ListFunctions::getName(), new ListFunctions());
$this->addAction(ListRuntimes::getName(), new ListRuntimes());
$this->addAction(CreateDeployment::getName(), new CreateDeployment());
$this->addAction(CreateTemplateDeployment::getName(), new CreateTemplateDeployment());
}
}
@@ -0,0 +1,165 @@
<?php
namespace Appwrite\Platform\Modules\Sites\Http\Deployments\Template;
use Appwrite\Event\Build;
use Appwrite\Event\Event;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Swoole\Request;
use Utopia\System\System;
use Utopia\Validator\Boolean;
use Utopia\Validator\Text;
use Utopia\VCS\Adapter\Git\GitHub;
class Create extends Base
{
use HTTP;
public static function getName()
{
return 'createTemplateDeployment';
}
public function __construct()
{
$this
->setHttpMethod(Action::HTTP_REQUEST_METHOD_POST)
->setHttpPath('/v1/sites/:siteId/deployments/template')
->desc('Create deployment')
->groups(['api', 'sites'])
->label('scope', 'sites.write')
->label('event', 'sites.[siteId].deployments.[deploymentId].create')
->label('audits.event', 'deployment.create')
->label('audits.resource', 'site/{request.siteId}')
->label('sdk', new Method(
namespace: 'sites',
name: 'createTemplateDeployment',
description: <<<EOT
Create a deployment based on a template.
Use this endpoint with combination of [listTemplates](https://appwrite.io/docs/server/sites#listTemplates) to find the template details.
EOT,
auth: [AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_ACCEPTED,
model: Response::MODEL_DEPLOYMENT,
)
],
))
->param('siteId', '', new UID(), 'Site ID.')
->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('activate', false, new Boolean(), 'Automatically activate the deployment when it is finished building.', true)
->inject('request')
->inject('response')
->inject('dbForProject')
->inject('dbForPlatform')
->inject('project')
->inject('queueForEvents')
->inject('queueForBuilds')
->inject('gitHub')
->callback([$this, 'action']);
}
public function action(string $siteId, string $repository, string $owner, string $rootDirectory, string $version, bool $activate, Request $request, Response $response, Database $dbForProject, Database $dbForPlatform, Document $project, Event $queueForEvents, Build $queueForBuilds, GitHub $github)
{
$site = $dbForProject->getDocument('sites', $siteId);
if ($site->isEmpty()) {
throw new Exception(Exception::SITE_NOT_FOUND);
}
$template = new Document([
'repositoryName' => $repository,
'ownerName' => $owner,
'rootDirectory' => $rootDirectory,
'version' => $version
]);
if (!empty($providerRepositoryId)) {
$installation = $dbForPlatform->getDocument('installations', $site->getAttribute('installationId'));
$deployment = $this->redeployVcsSite($request, $site, $project, $installation, $dbForProject, $dbForPlatform, $queueForBuilds, $template, $github);
$queueForEvents
->setParam('siteId', $site->getId())
->setParam('deploymentId', $deployment->getId());
$response
->setStatusCode(Response::STATUS_CODE_ACCEPTED)
->dynamic($deployment, Response::MODEL_DEPLOYMENT);
return;
}
$deploymentId = ID::unique();
$deployment = $dbForProject->createDocument('deployments', new Document([
'$id' => $deploymentId,
'$permissions' => [
Permission::read(Role::any()),
Permission::update(Role::any()),
Permission::delete(Role::any()),
],
'resourceId' => $site->getId(),
'resourceInternalId' => $site->getInternalId(),
'resourceType' => 'sites',
'installCommand' => $site->getAttribute('installCommand', ''),
'buildCommand' => $site->getAttribute('buildCommand', ''),
'outputDirectory' => $site->getAttribute('outputDirectory', ''),
'type' => 'manual',
'search' => implode(' ', [$deploymentId]),
'activate' => $activate,
]));
// Preview deployments url
$projectId = $project->getId();
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
$previewDomain = "{$deploymentId}-{$projectId}.{$sitesDomain}";
$rule = Authorization::skip(
fn () => $dbForPlatform->createDocument('rules', new Document([
'$id' => \md5($previewDomain),
'projectId' => $project->getId(),
'projectInternalId' => $project->getInternalId(),
'domain' => $previewDomain,
'resourceType' => 'deployment',
'resourceId' => $deploymentId,
'resourceInternalId' => $deployment->getInternalId(),
'status' => 'verified',
'certificateId' => '',
]))
);
$queueForBuilds
->setType(BUILD_TYPE_DEPLOYMENT)
->setResource($site)
->setDeployment($deployment)
->setTemplate($template);
$queueForEvents
->setParam('siteId', $site->getId())
->setParam('deploymentId', $deployment->getId());
$response
->setStatusCode(Response::STATUS_CODE_ACCEPTED)
->dynamic($deployment, Response::MODEL_DEPLOYMENT);
}
}
@@ -2,7 +2,6 @@
namespace Appwrite\Platform\Modules\Sites\Http\Sites;
use Appwrite\Event\Build;
use Appwrite\Event\Event;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
@@ -22,13 +21,11 @@ use Utopia\Database\Helpers\Role;
use Utopia\Database\Validator\Authorization;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Swoole\Request;
use Utopia\System\System;
use Utopia\Validator\Boolean;
use Utopia\Validator\Range;
use Utopia\Validator\Text;
use Utopia\Validator\WhiteList;
use Utopia\VCS\Adapter\Git\GitHub;
class Create extends Base
{
@@ -80,29 +77,21 @@ class Create extends Base
->param('providerBranch', '', new Text(128, 0), 'Production branch for the repo linked to the site.', true)
->param('providerSilentMode', false, new Boolean(), 'Is the VCS (Version Control System) connection in silent mode for the repo linked to the site? In silent mode, comments will not be made on commits and pull requests.', true)
->param('providerRootDirectory', '', new Text(128, 0), 'Path to site code in the linked repo.', true)
->param('templateRepository', '', new Text(128, 0), 'Repository name of the template.', true)
->param('templateOwner', '', new Text(128, 0), 'The name of the owner of the template.', true)
->param('templateRootDirectory', '', new Text(128, 0), 'Path to site code in the template repo.', true)
->param('templateVersion', '', new Text(128, 0), 'Version (tag) for the repo linked to the site template.', true)
->param('specification', APP_COMPUTE_SPECIFICATION_DEFAULT, fn (array $plan) => new FrameworkSpecification(
$plan,
Config::getParam('framework-specifications', []),
App::getEnv('_APP_COMPUTE_CPUS', APP_COMPUTE_CPUS_DEFAULT),
App::getEnv('_APP_COMPUTE_MEMORY', APP_COMPUTE_MEMORY_DEFAULT)
), 'Framework specification for the site and builds.', true, ['plan'])
->inject('request')
->inject('response')
->inject('dbForProject')
->inject('project')
->inject('user')
->inject('queueForEvents')
->inject('queueForBuilds')
->inject('dbForPlatform')
->inject('gitHub')
->callback([$this, 'action']);
}
public function action(string $siteId, string $name, string $framework, bool $enabled, int $timeout, string $installCommand, string $buildCommand, string $outputDirectory, string $buildRuntime, string $adapter, string $installationId, ?string $fallbackFile, string $providerRepositoryId, string $providerBranch, bool $providerSilentMode, string $providerRootDirectory, string $templateRepository, string $templateOwner, string $templateRootDirectory, string $templateVersion, string $specification, Request $request, Response $response, Database $dbForProject, Document $project, Document $user, Event $queueForEvents, Build $queueForBuilds, Database $dbForPlatform, GitHub $github)
public function action(string $siteId, string $name, string $framework, bool $enabled, int $timeout, string $installCommand, string $buildCommand, string $outputDirectory, string $buildRuntime, string $adapter, string $installationId, ?string $fallbackFile, string $providerRepositoryId, string $providerBranch, bool $providerSilentMode, string $providerRootDirectory, string $specification, Response $response, Database $dbForProject, Document $project, Event $queueForEvents, Database $dbForPlatform)
{
if (!empty($adapter)) {
$configFramework = Config::getParam('frameworks')[$framework] ?? [];
@@ -121,20 +110,6 @@ class Create extends Base
throw new Exception(Exception::SITE_FRAMEWORK_UNSUPPORTED, 'Framework "' . $framework . '" is not supported');
}
// build from template
$template = new Document([]);
if (
!empty($templateRepository)
&& !empty($templateOwner)
&& !empty($templateRootDirectory)
&& !empty($templateVersion)
) {
$template->setAttribute('repositoryName', $templateRepository)
->setAttribute('ownerName', $templateOwner)
->setAttribute('rootDirectory', $templateRootDirectory)
->setAttribute('version', $templateVersion);
}
$installation = $dbForPlatform->getDocument('installations', $installationId);
if (!empty($installationId) && $installation->isEmpty()) {
@@ -202,57 +177,6 @@ class Create extends Base
$site = $dbForProject->updateDocument('sites', $site->getId(), $site);
if (!empty($providerRepositoryId)) {
// Deploy VCS
$this->redeployVcsSite($request, $site, $project, $installation, $dbForProject, $dbForPlatform, $queueForBuilds, $template, $github);
} elseif (!$template->isEmpty()) {
// Deploy non-VCS from template
$deploymentId = ID::unique();
$deployment = $dbForProject->createDocument('deployments', new Document([
'$id' => $deploymentId,
'$permissions' => [
Permission::read(Role::any()),
Permission::update(Role::any()),
Permission::delete(Role::any()),
],
'resourceId' => $site->getId(),
'resourceInternalId' => $site->getInternalId(),
'resourceType' => 'sites',
'installCommand' => $site->getAttribute('installCommand', ''),
'buildCommand' => $site->getAttribute('buildCommand', ''),
'outputDirectory' => $site->getAttribute('outputDirectory', ''),
'type' => 'manual',
'search' => implode(' ', [$deploymentId]),
'activate' => true,
]));
// Preview deployments url
$projectId = $project->getId();
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
$previewDomain = "{$deploymentId}-{$projectId}.{$sitesDomain}";
$rule = Authorization::skip(
fn () => $dbForPlatform->createDocument('rules', new Document([
'$id' => \md5($previewDomain),
'projectId' => $project->getId(),
'projectInternalId' => $project->getInternalId(),
'domain' => $previewDomain,
'resourceType' => 'deployment',
'resourceId' => $deploymentId,
'resourceInternalId' => $deployment->getInternalId(),
'status' => 'verified',
'certificateId' => '',
]))
);
$queueForBuilds
->setType(BUILD_TYPE_DEPLOYMENT)
->setResource($site)
->setDeployment($deployment)
->setTemplate($template);
}
$queueForEvents->setParam('siteId', $site->getId());
$response
@@ -9,6 +9,7 @@ use Appwrite\Platform\Modules\Sites\Http\Deployments\Create as CreateDeployment;
use Appwrite\Platform\Modules\Sites\Http\Deployments\Delete as DeleteDeployment;
use Appwrite\Platform\Modules\Sites\Http\Deployments\Download\Get as DownloadDeployment;
use Appwrite\Platform\Modules\Sites\Http\Deployments\Get as GetDeployment;
use Appwrite\Platform\Modules\Sites\Http\Deployments\Template\Create as CreateTemplateDeployment;
use Appwrite\Platform\Modules\Sites\Http\Deployments\Update as UpdateDeployment;
use Appwrite\Platform\Modules\Sites\Http\Deployments\XList as ListDeployments;
use Appwrite\Platform\Modules\Sites\Http\Frameworks\XList as ListFrameworks;
@@ -49,6 +50,7 @@ class Http extends Service
// Deployments
$this->addAction(CreateDeployment::getName(), new CreateDeployment());
$this->addAction(CreateTemplateDeployment::getName(), new CreateTemplateDeployment());
$this->addAction(GetDeployment::getName(), new GetDeployment());
$this->addAction(ListDeployments::getName(), new ListDeployments());
$this->addAction(UpdateDeployment::getName(), new UpdateDeployment());
+32
View File
@@ -0,0 +1,32 @@
<?php
namespace Appwrite\Transformation;
abstract class Adapter
{
protected mixed $input;
protected mixed $output;
public function __construct()
{
}
public function setInput(mixed $input): self
{
$this->input = $input;
return $this;
}
public function getOutput(): mixed
{
return $this->output;
}
/**
* @param array<mixed> $traits
*/
abstract public function isValid(array $traits): bool;
abstract public function transform(): void;
}
@@ -0,0 +1,26 @@
<?php
namespace Appwrite\Transformation\Adapter;
use Appwrite\Transformation\Adapter;
class Mock extends Adapter
{
/**
* @param array<mixed> $traits Mock traits
*/
public function isValid(array $traits): bool
{
if ($traits['mock'] === true) {
return true;
}
return false;
}
public function transform(): void
{
$this->output = $this->input;
$this->output = "Mock: " . $this->output;
}
}
@@ -0,0 +1,201 @@
<?php
namespace Appwrite\Transformation\Adapter;
use Appwrite\Transformation\Adapter;
class Preview extends Adapter
{
/**
* @param array<mixed> $traits Proxied response headers
*/
public function isValid(array $traits): bool
{
$contentType = '';
foreach ($traits as $key => $value) {
if (\strtolower($key) === 'content-type') {
$contentType = $value;
break;
}
}
if (\str_contains($contentType, 'text/html')) {
return true;
}
return false;
}
public function transform(): void
{
$this->output = $this->input;
$banner = <<<EOT
<style>
#appwrite-preview {
padding: 0;
margin: 0;
position: fixed;
right: 16px;
bottom: 16px;
z-index: 1;
border-radius: var(--border-radius-S, 8px);
border: var(--border-width-S, 1px) solid var(--color-border-neutral, #EDEDF0);
background: var(--color-bgColor-neutral-primary, #FFF);
box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.03), 0px 4px 4px 0px rgba(0, 0, 0, 0.04);
padding: var(--space-3, 6px) var(--space-4, 8px);
display: flex;
justify-content: center;
align-items: center;
gap: var(--gap-XXS, 4px);
cursor: pointer;
transition: opacity 0.3s;
}
#appwrite-preview-close {
position: absolute;
right: 0px;
bottom: 0px;
border-radius: var(--border-radius-S, 8px);
background: linear-gradient(270deg, #FFF 69.64%, rgba(255, 255, 255, 0.00) 114.29%);
height: 100%;
aspect-ratio: 1 / 1;
display: flex;
justify-content: center;
align-items: center;
opacity: 0;
transition: opacity 0.3s;
}
#appwrite-preview-logo-dark {
display: none;
}
#appwrite-preview:hover #appwrite-preview-close {
opacity: 1;
}
#appwrite-preview-text {
padding: 0;
margin: 0;
color: var(--color-fgColor-neutral-secondary, #56565C);
font-family: var(--font-family-sansSerif, Inter);
font-size: var(--font-size-XS, 12px);
font-style: normal;
font-weight: 500;
line-height: 130%;
letter-spacing: -0.12px;
}
#appwrite-preview-close-text {
opacity: 0;
transition: opacity 0.3s;
position: absolute;
bottom: calc(15px + 4px);
display: flex;
padding: var(--space-1, 2px) var(--space-2, 4px);
color: var(--color-fgColor-neutral-secondary, #56565C);
text-align: center;
font-family: var(--font-family-sansSerif, Inter);
font-size: var(--font-size-XS, 12px);
font-style: normal;
font-weight: 400;
line-height: 130%;
letter-spacing: -0.12px;
border-radius: var(--border-radius-XS, 6px);
background: #EDEDF0;
}
#appwrite-preview-close:hover #appwrite-preview-close-text {
opacity: 1;
}
@media (prefers-color-scheme: dark) {
#appwrite-preview {
border: var(--border-width-S, 1px) solid var(--color-border-neutral, #2D2D31);
background: var(--color-bgColor-neutral-primary, #1D1D21);
box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.03), 0px 4px 4px 0px rgba(0, 0, 0, 0.04);
}
#appwrite-preview-text {
color: var(--color-fgColor-neutral-secondary, #C3C3C6);
font-family: var(--font-family-sansSerif, Inter);
font-size: var(--font-size-XS, 12px);
}
#appwrite-preview-logo-dark {
display: block;
}
#appwrite-preview-logo-light {
display: none;
}
#appwrite-preview-close {
background: linear-gradient(270deg, #1D1D21 69.64%, rgba(29, 29, 33, 0.00) 114.29%);
}
#appwrite-preview-close-text {
background: #2D2D31;
color: var(--color-fgColor-neutral-secondary, #C3C3C6);
}
}
</style>
<button id="appwrite-preview">
<p id="appwrite-preview-text">Preview by</p>
<div id="appwrite-preview-close">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.43451 3.43439C3.74693 3.12197 4.25346 3.12197 4.56588 3.43439L8.0002 6.8687L11.4345 3.43439C11.7469 3.12197 12.2535 3.12197 12.5659 3.43439C12.8783 3.74681 12.8783 4.25334 12.5659 4.56576L9.13157 8.00007L12.5659 11.4344C12.8783 11.7468 12.8783 12.2533 12.5659 12.5658C12.2535 12.8782 11.7469 12.8782 11.4345 12.5658L8.0002 9.13144L4.56588 12.5658C4.25346 12.8782 3.74693 12.8782 3.43451 12.5658C3.12209 12.2533 3.12209 11.7468 3.43451 11.4344L6.86882 8.00007L3.43451 4.56576C3.12209 4.25334 3.12209 3.74681 3.43451 3.43439Z" fill="#97979B"/>
</svg>
<p id="appwrite-preview-close-text">Hide</p>
</div>
<svg id="appwrite-preview-logo-light" width="65" height="12" viewBox="0 0 65 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M18.9862 9.74762C20.0493 9.74762 20.5867 9.191 20.8204 8.81202H20.9255C20.9722 9.21468 21.2526 9.59366 21.8017 9.59366H22.8414V8.40936H22.5727C22.3858 8.40936 22.2924 8.30277 22.2924 8.13697V3.38795H20.9138V4.1459H20.8087C20.54 3.76692 19.9792 3.23399 18.9511 3.23399C17.3156 3.23399 16.1006 4.60777 16.1006 6.4908C16.1006 8.37383 17.3389 9.74762 18.9862 9.74762ZM19.2315 8.39752C18.2619 8.39752 17.5025 7.6751 17.5025 6.50265C17.5025 5.35388 18.2385 4.57224 19.2198 4.57224C20.1544 4.57224 20.9372 5.27098 20.9372 6.50265C20.9372 7.55667 20.2713 8.39752 19.2315 8.39752Z" fill="#2D2D31"/>
<path d="M23.6553 12H25.0339V8.81202H25.139C25.396 9.191 25.9451 9.74762 27.0316 9.74762C28.6672 9.74762 29.8588 8.35015 29.8588 6.4908C29.8588 4.61962 28.5854 3.23399 26.9381 3.23399C25.8867 3.23399 25.3727 3.81429 25.1273 4.13405H25.0222V3.38795H23.6553V12ZM26.7395 8.43305C25.7933 8.43305 25.0105 7.72247 25.0105 6.4908C25.0105 5.43678 25.6764 4.54856 26.7162 4.54856C27.6858 4.54856 28.4452 5.31835 28.4452 6.4908C28.4452 7.63957 27.7092 8.43305 26.7395 8.43305Z" fill="#2D2D31"/>
<path d="M30.5701 12H31.9487V8.81202H32.0538C32.3108 9.191 32.8599 9.74762 33.9464 9.74762C35.582 9.74762 36.66 8.35015 36.66 6.4908C36.66 4.61962 35.5002 3.23399 33.8529 3.23399C32.8015 3.23399 32.2875 3.81429 32.0421 4.13405H31.937V3.38795H30.5701V12ZM33.6543 8.43305C32.708 8.43305 31.9253 7.72247 31.9253 6.4908C31.9253 5.43678 32.5912 4.54856 33.631 4.54856C34.6006 4.54856 35.36 5.31835 35.36 6.4908C35.36 7.63957 34.624 8.43305 33.6543 8.43305Z" fill="#2D2D31"/>
<path d="M38.4823 9.73776H40.4333L41.5431 4.87031H41.6132L42.7231 9.73776H44.6624L46.2153 3.53205H44.8259L43.7161 8.41135H43.6109L42.5011 3.53205H40.6669L39.5454 8.41135H39.4403L38.3421 3.53205H36.8701L38.4823 9.73776Z" fill="#2D2D31"/>
<path d="M46.9137 9.73776H48.2923V6.67044C48.2923 5.49798 48.8297 4.77556 49.8344 4.77556H50.4418V3.37809H49.9862C49.2035 3.37809 48.6077 3.92287 48.374 4.44396H48.2806V3.53205H46.9137V9.73776Z" fill="#2D2D31"/>
<path d="M57.2829 9.73776H58.3577V8.49425H57.2946C56.874 8.49425 56.6988 8.30476 56.6988 7.86657V4.76372H58.4278V3.53205H56.6988V1.79114H55.3903V3.53205H54.2454V4.76372H55.3085V7.87842C55.3085 9.19299 56.0913 9.73776 57.2829 9.73776Z" fill="#2D2D31"/>
<path d="M62.0561 9.74762C63.3295 9.74762 64.451 9.1081 64.8482 7.81721L63.5865 7.5093C63.3645 8.19619 62.722 8.55148 62.0444 8.55148C61.0397 8.55148 60.3738 7.88827 60.3621 6.84609H65.0001V6.45527C65.0001 4.60777 63.8669 3.23399 61.9977 3.23399C60.3504 3.23399 58.9368 4.54856 58.9368 6.50265C58.9368 8.39752 60.1869 9.74762 62.0561 9.74762ZM60.3738 5.8276C60.4556 5.08149 61.1215 4.45381 61.9977 4.45381C62.8388 4.45381 63.5281 4.98675 63.5982 5.8276H60.3738Z" fill="#2D2D31"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M53.6325 9.73776H52.2539V4.76372H51.1791V3.53205H53.6325V9.73776Z" fill="#2D2D31"/>
<path d="M52.841 2.67085C53.3434 2.67085 53.7172 2.29187 53.7172 1.79447C53.7172 1.30891 53.3434 0.929932 52.841 0.929932C52.3387 0.929932 51.9648 1.30891 51.9648 1.79447C51.9648 2.29187 52.3387 2.67085 52.841 2.67085Z" fill="#2D2D31"/>
<path d="M12.0363 8.21609V10.9548H5.29451C3.33035 10.9548 1.61537 9.85334 0.697814 8.21609C0.564426 7.97807 0.447681 7.72836 0.349751 7.46917C0.157509 6.96127 0.0366636 6.41627 0 5.84762V5.10717C0.00795985 4.98044 0.0205026 4.85471 0.0369048 4.73048C0.0704326 4.47553 0.121086 4.22606 0.18766 3.98356C0.817453 1.68455 2.86531 0 5.29451 0C7.72371 0 9.77132 1.68455 10.4011 3.98356H7.51844C7.04519 3.23415 6.22605 2.7387 5.29451 2.7387C4.36296 2.7387 3.54382 3.23415 3.07057 3.98356C2.92633 4.21137 2.81441 4.46258 2.74108 4.73048C2.67596 4.968 2.64122 5.21846 2.64122 5.47739C2.64122 6.2624 2.96106 6.96999 3.47387 7.46917C3.94905 7.93251 4.5897 8.21609 5.29451 8.21609H12.0363Z" fill="#FD366E"/>
<path d="M12.0364 4.73047V7.46917H7.11523C7.62804 6.96998 7.94788 6.2624 7.94788 5.47739C7.94788 5.21846 7.91315 4.96799 7.84802 4.73047H12.0364Z" fill="#FD366E"/>
</svg>
<svg id="appwrite-preview-logo-dark" width="65" height="12" viewBox="0 0 65 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M18.9862 9.74762C20.0493 9.74762 20.5867 9.191 20.8204 8.81202H20.9255C20.9722 9.21468 21.2526 9.59366 21.8017 9.59366H22.8414V8.40936H22.5727C22.3858 8.40936 22.2924 8.30277 22.2924 8.13697V3.38795H20.9138V4.1459H20.8087C20.54 3.76692 19.9792 3.23399 18.9511 3.23399C17.3156 3.23399 16.1006 4.60777 16.1006 6.4908C16.1006 8.37383 17.3389 9.74762 18.9862 9.74762ZM19.2315 8.39752C18.2619 8.39752 17.5025 7.6751 17.5025 6.50265C17.5025 5.35388 18.2385 4.57224 19.2198 4.57224C20.1544 4.57224 20.9372 5.27098 20.9372 6.50265C20.9372 7.55667 20.2713 8.39752 19.2315 8.39752Z" fill="#EDEDF0"/>
<path d="M23.6553 12H25.0339V8.81202H25.139C25.396 9.191 25.9451 9.74762 27.0316 9.74762C28.6672 9.74762 29.8588 8.35015 29.8588 6.4908C29.8588 4.61962 28.5854 3.23399 26.9381 3.23399C25.8867 3.23399 25.3727 3.81429 25.1273 4.13405H25.0222V3.38795H23.6553V12ZM26.7395 8.43305C25.7933 8.43305 25.0105 7.72247 25.0105 6.4908C25.0105 5.43678 25.6764 4.54856 26.7162 4.54856C27.6858 4.54856 28.4452 5.31835 28.4452 6.4908C28.4452 7.63957 27.7092 8.43305 26.7395 8.43305Z" fill="#EDEDF0"/>
<path d="M30.5701 12H31.9487V8.81202H32.0538C32.3108 9.191 32.8599 9.74762 33.9464 9.74762C35.582 9.74762 36.66 8.35015 36.66 6.4908C36.66 4.61962 35.5002 3.23399 33.8529 3.23399C32.8015 3.23399 32.2875 3.81429 32.0421 4.13405H31.937V3.38795H30.5701V12ZM33.6543 8.43305C32.708 8.43305 31.9253 7.72247 31.9253 6.4908C31.9253 5.43678 32.5912 4.54856 33.631 4.54856C34.6006 4.54856 35.36 5.31835 35.36 6.4908C35.36 7.63957 34.624 8.43305 33.6543 8.43305Z" fill="#EDEDF0"/>
<path d="M38.4823 9.73776H40.4333L41.5431 4.87031H41.6132L42.7231 9.73776H44.6624L46.2153 3.53205H44.8259L43.7161 8.41135H43.6109L42.5011 3.53205H40.6669L39.5454 8.41135H39.4403L38.3421 3.53205H36.8701L38.4823 9.73776Z" fill="#EDEDF0"/>
<path d="M46.9137 9.73776H48.2923V6.67044C48.2923 5.49798 48.8297 4.77556 49.8344 4.77556H50.4418V3.37809H49.9862C49.2035 3.37809 48.6077 3.92287 48.374 4.44396H48.2806V3.53205H46.9137V9.73776Z" fill="#EDEDF0"/>
<path d="M57.2829 9.73776H58.3577V8.49425H57.2946C56.874 8.49425 56.6988 8.30476 56.6988 7.86657V4.76372H58.4278V3.53205H56.6988V1.79114H55.3903V3.53205H54.2454V4.76372H55.3085V7.87842C55.3085 9.19299 56.0913 9.73776 57.2829 9.73776Z" fill="#EDEDF0"/>
<path d="M62.0561 9.74762C63.3295 9.74762 64.451 9.1081 64.8482 7.81721L63.5865 7.5093C63.3645 8.19619 62.722 8.55148 62.0444 8.55148C61.0397 8.55148 60.3738 7.88827 60.3621 6.84609H65.0001V6.45527C65.0001 4.60777 63.8669 3.23399 61.9977 3.23399C60.3504 3.23399 58.9368 4.54856 58.9368 6.50265C58.9368 8.39752 60.1869 9.74762 62.0561 9.74762ZM60.3738 5.8276C60.4556 5.08149 61.1215 4.45381 61.9977 4.45381C62.8388 4.45381 63.5281 4.98675 63.5982 5.8276H60.3738Z" fill="#EDEDF0"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M53.6325 9.73776H52.2539V4.76372H51.1791V3.53205H53.6325V9.73776Z" fill="#EDEDF0"/>
<path d="M52.841 2.67085C53.3434 2.67085 53.7172 2.29187 53.7172 1.79447C53.7172 1.30891 53.3434 0.929932 52.841 0.929932C52.3387 0.929932 51.9648 1.30891 51.9648 1.79447C51.9648 2.29187 52.3387 2.67085 52.841 2.67085Z" fill="#EDEDF0"/>
<path d="M12.0363 8.21609V10.9548H5.29451C3.33035 10.9548 1.61537 9.85334 0.697814 8.21609C0.564426 7.97807 0.447681 7.72836 0.349751 7.46917C0.157509 6.96127 0.0366636 6.41627 0 5.84762V5.10717C0.00795985 4.98044 0.0205026 4.85471 0.0369048 4.73048C0.0704326 4.47553 0.121086 4.22606 0.18766 3.98356C0.817453 1.68455 2.86531 0 5.29451 0C7.72371 0 9.77132 1.68455 10.4011 3.98356H7.51844C7.04519 3.23415 6.22605 2.7387 5.29451 2.7387C4.36296 2.7387 3.54382 3.23415 3.07057 3.98356C2.92633 4.21137 2.81441 4.46258 2.74108 4.73048C2.67596 4.968 2.64122 5.21846 2.64122 5.47739C2.64122 6.2624 2.96106 6.96999 3.47387 7.46917C3.94905 7.93251 4.5897 8.21609 5.29451 8.21609H12.0363Z" fill="#FD366E"/>
<path d="M12.0364 4.73047V7.46917H7.11523C7.62804 6.96998 7.94788 6.2624 7.94788 5.47739C7.94788 5.21846 7.91315 4.96799 7.84802 4.73047H12.0364Z" fill="#FD366E"/>
</svg>
</button>
<script>
(function() {
var banner = document.getElementById("appwrite-preview");
banner.addEventListener("click", function() {
banner.style.opacity = 0;
setTimeout(() => {
banner.style.display = 'none';
}, 350);
});
})();
</script>
EOT;
$this->output .= $banner;
}
}
@@ -0,0 +1,74 @@
<?php
namespace Appwrite\Transformation;
class Transformation
{
/**
* @var array<Adapter> $adapters
*/
protected array $adapters;
/**
* @var array<mixed> $traits
*/
protected array $traits;
protected mixed $input;
protected mixed $output;
/**
* @param array<Adapter> $adapters
*/
public function __construct(array $adapters = [])
{
$this->adapters = $adapters;
}
/**
* @param array<mixed> $traits
*/
public function setTraits(array $traits): self
{
$this->traits = $traits;
return $this;
}
public function setInput(mixed $input): self
{
$this->input = $input;
return $this;
}
public function addAdapter(Adapter $adapter): self
{
$this->adapters[] = $adapter;
return $this;
}
public function transform(): bool
{
foreach ($this->adapters as $adapter) {
if (!$adapter->isValid($this->traits)) {
return false;
}
}
$output = $this->input;
foreach ($this->adapters as $adapter) {
$adapter->setInput($output);
$adapter->transform();
$output = $adapter->getOutput();
}
$this->output = $output;
return true;
}
public function getOutput(): mixed
{
return $this->output;
}
}
@@ -209,6 +209,16 @@ trait FunctionsBase
return $deployment;
}
protected function createTemplateDeployment(string $functionId, mixed $params = []): mixed
{
$deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments/template', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), $params);
return $deployment;
}
protected function getFunctionUsage(string $functionId, mixed $params): mixed
{
$usage = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/usage', array_merge([
@@ -358,6 +358,21 @@ class FunctionsCustomServerTest extends Scope
$functionId = $function['body']['$id'] ?? '';
$deployment = $this->createTemplateDeployment(
$functionId,
[
'functionId' => ID::unique(),
'activate' => true,
'repository' => $starterTemplate['body']['providerRepositoryId'],
'owner' => $starterTemplate['body']['providerOwner'],
'rootDirectory' => $phpRuntime['providerRootDirectory'],
'version' => $starterTemplate['body']['providerVersion'],
]
);
$this->assertEquals(202, $deployment['headers']['status-code']);
$this->assertNotEmpty($deployment['body']['$id']);
$deployments = $this->listDeployments($functionId);
$this->assertEquals(200, $deployments['headers']['status-code']);
+33
View File
@@ -230,6 +230,16 @@ trait SitesBase
return $deployment;
}
protected function createTemplateDeployment(string $siteId, mixed $params = []): mixed
{
$deployment = $this->client->call(Client::METHOD_POST, '/sites/' . $siteId . '/deployments/template', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), $params);
return $deployment;
}
protected function getSiteUsage(string $siteId, mixed $params): mixed
{
$usage = $this->client->call(Client::METHOD_GET, '/sites/' . $siteId . '/usage', array_merge([
@@ -301,4 +311,27 @@ trait SitesBase
return $domain;
}
protected function getDeploymentDomain(string $deploymentId): string
{
$rules = $this->client->call(Client::METHOD_GET, '/proxy/rules', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [
Query::equal('resourceId', [$deploymentId])->toString(),
Query::equal('resourceType', ['deployment'])->toString(),
],
]);
$this->assertEquals(200, $rules['headers']['status-code']);
$this->assertGreaterThanOrEqual(1, $rules['body']['total']);
$this->assertGreaterThanOrEqual(1, \count($rules['body']['rules']));
$this->assertNotEmpty($rules['body']['rules'][0]['domain']);
$domain = $rules['body']['rules'][0]['domain'];
return $domain;
}
}
@@ -570,11 +570,6 @@ class SitesCustomServerTest extends Scope
'installCommand' => $nextjsFramework['installCommand'],
'outputDirectory' => $nextjsFramework['outputDirectory'],
'providerRootDirectory' => $nextjsFramework['providerRootDirectory'],
'templateOwner' => $starterTemplate['body']['providerOwner'],
'templateRepository' => $starterTemplate['body']['providerRepositoryId'],
'templateRootDirectory' => $nextjsFramework['providerRootDirectory'],
'templateVersion' => $starterTemplate['body']['providerVersion'],
'providerBranch' => 'main',
]
);
@@ -583,6 +578,20 @@ class SitesCustomServerTest extends Scope
$siteId = $site['body']['$id'] ?? '';
$deployment = $this->createTemplateDeployment(
$siteId,
[
'owner' => $starterTemplate['body']['providerOwner'],
'repository' => $starterTemplate['body']['providerRepositoryId'],
'rootDirectory' => $nextjsFramework['providerRootDirectory'],
'version' => $starterTemplate['body']['providerVersion'],
'activate' => true,
]
);
$this->assertEquals(202, $deployment['headers']['status-code']);
$this->assertNotEmpty($deployment['body']['$id']);
$deployments = $this->listDeployments($siteId);
$this->assertEquals(200, $deployments['headers']['status-code']);
@@ -1218,12 +1227,21 @@ class SitesCustomServerTest extends Scope
'buildCommand' => $template['frameworks'][0]['buildCommand'],
'installCommand' => $template['frameworks'][0]['installCommand'],
'fallbackFile' => $template['frameworks'][0]['fallbackFile'],
'templateRepository' => $template['providerRepositoryId'],
'templateOwner' => $template['providerOwner'],
'templateRootDirectory' => $template['frameworks'][0]['providerRootDirectory'],
'templateVersion' => $template['providerVersion'],
]);
$this->assertNotEmpty($siteId);
$deployment = $this->createTemplateDeployment($siteId, [
'repository' => $template['providerRepositoryId'],
'owner' => $template['providerOwner'],
'rootDirectory' => $template['frameworks'][0]['providerRootDirectory'],
'version' => $template['providerVersion'],
'activate' => true
]);
$this->assertEquals(202, $deployment['headers']['status-code']);
$this->assertNotEmpty($deployment['body']['$id']);
$this->assertEventually(function () use ($siteId) {
$site = $this->getSite($siteId);
$this->assertNotEmpty($site['body']['deploymentId']);
@@ -1366,5 +1384,120 @@ class SitesCustomServerTest extends Scope
$this->cleanupSite($site['body']['$id']);
}
public function testSitePreviewBranding(): void
{
$siteId = $this->setupSite([
'siteId' => ID::unique(),
'name' => 'A site',
'framework' => 'other',
'adapter' => 'static',
'buildRuntime' => 'static-1',
'outputDirectory' => './',
'buildCommand' => '',
'installCommand' => '',
'fallbackFile' => '',
]);
$this->assertNotEmpty($siteId);
$deploymentId = $this->setupDeployment($siteId, [
'code' => $this->packageSite('static'),
'activate' => 'true'
]);
$this->assertNotEmpty($deploymentId);
$domain = $this->getSiteDomain($siteId);
$previewDomain = $this->getDeploymentDomain($deploymentId);
$this->assertNotEmpty($domain);
$this->assertNotEmpty($previewDomain);
$proxyClient = new Client();
$proxyClient->setEndpoint('http://' . $domain);
$response = $proxyClient->call(Client::METHOD_GET, '/', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
]));
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertStringContainsString("Hello Appwrite", $response['body']);
$this->assertStringNotContainsString("Preview by", $response['body']);
$contentLength = $response['headers']['content-length'];
$proxyClient = new Client();
$proxyClient->setEndpoint('http://' . $previewDomain);
$response = $proxyClient->call(Client::METHOD_GET, '/', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
]));
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertStringContainsString("Hello Appwrite", $response['body']);
$this->assertStringContainsString("Preview by", $response['body']);
$this->assertGreaterThan($contentLength, $response['headers']['content-length']);
$this->cleanupSite($siteId);
}
public function testSiteCors(): void
{
// Create rule together with site
$subdomain = 'startup' . \uniqid();
$siteId = $this->setupSite([
'siteId' => ID::unique(),
'name' => 'Startup site',
'framework' => 'other',
'adapter' => 'static',
'buildRuntime' => 'static-1',
'outputDirectory' => './',
'buildCommand' => '',
'installCommand' => '',
'fallbackFile' => '',
'subdomain' => $subdomain
]);
$this->assertNotEmpty($siteId);
$domain = $this->getSiteDomain($siteId);
$this->assertNotEmpty($domain);
$url = 'http://' . $domain;
$response = $this->client->call(Client::METHOD_GET, '/account', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'referer' => $url,
'origin' => $url
]));
$this->assertEquals($url, $response['headers']['access-control-allow-origin']);
$response = $this->client->call(Client::METHOD_GET, '/account', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => 'unknown',
'referer' => $url,
'origin' => $url
]));
$this->assertNotEquals($url, $response['headers']['access-control-allow-origin']);
$this->assertEquals('http://localhost', $response['headers']['access-control-allow-origin']);
$response = $this->client->call(Client::METHOD_GET, '/account', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'referer' => 'http://unknown.com',
'origin' => 'http://unknown.com'
]));
$this->assertNotEquals($url, $response['headers']['access-control-allow-origin']);
$this->assertEquals('http://localhost', $response['headers']['access-control-allow-origin']);
}
// TODO: Add tests for deletion of resources when site is deleted
}
@@ -0,0 +1,43 @@
<?php
namespace Tests\Unit\Transformation;
use Appwrite\Transformation\Adapter\Mock;
use Appwrite\Transformation\Adapter\Preview;
use Appwrite\Transformation\Transformation;
use PHPUnit\Framework\TestCase;
class TransformationTest extends TestCase
{
public function testPreview(): void
{
$input = "Hello world";
$transformer = new Transformation([new Preview()]);
$transformer->addAdapter(new Mock());
$transformer->setInput($input);
$transformer->setTraits([]);
$this->assertFalse($transformer->transform());
$transformer->setTraits(['mock' => true]);
$this->assertFalse($transformer->transform());
$transformer->setTraits(['mock' => true, 'content-type' => 'text/plain']);
$this->assertFalse($transformer->transform());
$transformer->setTraits(['mock' => true, 'content-type' => 'tExT/HtML']);
$this->assertFalse($transformer->transform());
$transformer->setTraits(['mock' => false, 'content-type' => 'text/plain, text/html; charset=utf-8']);
$this->assertFalse($transformer->transform());
$transformer->setTraits(['mock' => true, 'content-type' => 'text/plain, text/html; charset=utf-8']);
$this->assertTrue($transformer->transform());
$this->assertStringContainsString("Hello world", $transformer->getOutput());
$this->assertStringContainsString("Preview by", $transformer->getOutput());
$this->assertStringContainsString("Mock:", $transformer->getOutput());
}
}