Merge branch 'feat-sites' into feat-zip-deployment

This commit is contained in:
Matej Bačo
2025-02-13 09:38:56 +01:00
committed by GitHub
27 changed files with 741 additions and 18 deletions
+33
View File
@@ -1185,6 +1185,39 @@ return [
'default' => false,
'array' => false,
],
[
'$id' => ID::custom('personalAccessToken'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 256,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => ['encrypt'],
],
[
'$id' => ID::custom('personalAccessTokenExpiry'),
'type' => Database::VAR_DATETIME,
'format' => '',
'size' => 0,
'signed' => false,
'required' => false,
'default' => null,
'array' => false,
'filters' => ['datetime'],
],
[
'$id' => ID::custom('personalRefreshToken'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 256,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => ['encrypt'],
],
],
'indexes' => [
+12
View File
@@ -375,6 +375,13 @@ return [
'code' => 409,
],
/** Console */
Exception::RESOURCE_ALREADY_EXISTS => [
'name' => Exception::RESOURCE_ALREADY_EXISTS,
'description' => 'Resource with the requested ID already exists. Please choose a different ID and try again.',
'code' => 409,
],
/** Membership */
Exception::MEMBERSHIP_NOT_FOUND => [
'name' => Exception::MEMBERSHIP_NOT_FOUND,
@@ -868,6 +875,11 @@ return [
'description' => 'Variable with the same ID already exists in this project. Try again with a different ID.',
'code' => 409,
],
Exception::VARIABLE_CANNOT_UNSET_SECRET => [
'name' => Exception::VARIABLE_CANNOT_UNSET_SECRET,
'description' => 'Secret variables cannot be marked as non-secret. Please re-create the variable if this is your intention.',
'code' => 400,
],
Exception::GRAPHQL_NO_QUERY => [
'name' => Exception::GRAPHQL_NO_QUERY,
'description' => 'Param "query" is not optional.',
+1
View File
@@ -26,6 +26,7 @@ $member = [
'subscribers.write',
'subscribers.read',
'assistant.read',
'rules.read'
];
$admins = [
+50
View File
@@ -127,6 +127,56 @@ return [
'providerVersion' => '0.2.*',
'variables' => [],
],
[
'key' => 'sveltekit-with-appwrite',
'name' => 'SvelteKit with Appwrite',
'useCases' => ['starter'],
'demoUrl' => '',
'demoImage' => '',
'frameworks' => [
getFramework('SVELTEKIT', [
'providerRootDirectory' => './',
]),
],
'vcsProvider' => 'github',
'providerRepositoryId' => 'starter-for-svelte',
'providerOwner' => 'appwrite',
'providerVersion' => '0.1.*',
'variables' => [
[
'name' => 'PUBLIC_APPWRITE_ENDPOINT',
'description' => 'Endpoint of Appwrite server.',
'value' => 'https://cloud.appwrite.io/v1',
'placeholder' => 'https://cloud.appwrite.io/v1',
'required' => true,
'type' => 'text'
],
[
'name' => 'PUBLIC_APPWRITE_PROJECT_ID',
'description' => 'Your Appwrite project ID.',
'value' => '',
'placeholder' => '67ab5b6d000db4f5f59d',
'required' => true,
'type' => 'text'
],
[
'name' => 'PUBLIC_APPWRITE_PROJECT_NAME',
'description' => 'Your Appwrite project name.',
'value' => '',
'placeholder' => 'My project',
'required' => true,
'type' => 'text'
],
[
'name' => 'PUBLIC_APPWRITE_VERSION',
'description' => 'Version of Appwrite server.',
'value' => '1.7.0',
'placeholder' => '1.7.0',
'required' => true,
'type' => 'text'
],
],
],
[
'key' => 'astro-starter',
'name' => 'Astro starter website',
+1
View File
@@ -7,4 +7,5 @@ return [
"gif" => "image/gif",
"png" => "image/png",
"heic" => "image/heic",
"webp" => "image/webp",
];
+8 -2
View File
@@ -1585,7 +1585,7 @@ App::post('/v1/functions/:functionId/variables')
->param('functionId', '', new UID(), 'Function unique ID.', false)
->param('key', null, new Text(Database::LENGTH_KEY), 'Variable key. Max length: ' . Database::LENGTH_KEY . ' chars.', false)
->param('value', null, new Text(8192, 0), 'Variable value. Max length: 8192 chars.', false)
->param('secret', false, new Boolean(), 'Is secret? Secret variables can only be updated or deleted, they cannot be read.', true)
->param('secret', true, new Boolean(), 'Secret variables can be updated or deleted, but only functions can read them during build and runtime.', true)
->inject('response')
->inject('dbForProject')
->inject('dbForPlatform')
@@ -1729,10 +1729,11 @@ App::put('/v1/functions/:functionId/variables/:variableId')
->param('variableId', '', new UID(), 'Variable unique ID.', false)
->param('key', null, new Text(255), 'Variable key. Max length: 255 chars.', false)
->param('value', null, new Text(8192, 0), 'Variable value. Max length: 8192 chars.', true)
->param('secret', null, new Boolean(), 'Secret variables can be updated or deleted, but only functions can read them during build and runtime.', true)
->inject('response')
->inject('dbForProject')
->inject('dbForPlatform')
->action(function (string $functionId, string $variableId, string $key, ?string $value, Response $response, Database $dbForProject, Database $dbForPlatform) {
->action(function (string $functionId, string $variableId, string $key, ?string $value, ?bool $secret, Response $response, Database $dbForProject, Database $dbForPlatform) {
$function = $dbForProject->getDocument('functions', $functionId);
@@ -1749,9 +1750,14 @@ App::put('/v1/functions/:functionId/variables/:variableId')
throw new Exception(Exception::VARIABLE_NOT_FOUND);
}
if ($variable->getAttribute('secret') === true && $secret === false) {
throw new Exception(Exception::VARIABLE_CANNOT_UNSET_SECRET);
}
$variable
->setAttribute('key', $key)
->setAttribute('value', $value ?? $variable->getAttribute('value'))
->setAttribute('secret', $secret ?? $variable->getAttribute('secret'))
->setAttribute('search', implode(' ', [$variableId, $function->getId(), $key, 'function']));
try {
+8 -2
View File
@@ -388,7 +388,7 @@ App::post('/v1/project/variables')
))
->param('key', null, new Text(Database::LENGTH_KEY), 'Variable key. Max length: ' . Database::LENGTH_KEY . ' chars.', false)
->param('value', null, new Text(8192, 0), 'Variable value. Max length: 8192 chars.', false)
->param('secret', false, new Boolean(), 'Is secret? Secret variables can only be updated or deleted, they cannot be read.', true)
->param('secret', true, new Boolean(), 'Secret variables can be updated or deleted, but only projects can read them during build and runtime.', true)
->inject('project')
->inject('response')
->inject('dbForProject')
@@ -509,19 +509,25 @@ App::put('/v1/project/variables/:variableId')
->param('variableId', '', new UID(), 'Variable unique ID.', false)
->param('key', null, new Text(255), 'Variable key. Max length: 255 chars.', false)
->param('value', null, new Text(8192, 0), 'Variable value. Max length: 8192 chars.', true)
->param('secret', null, new Boolean(), 'Secret variables can be updated or deleted, but only projects can read them during build and runtime.', true)
->inject('project')
->inject('response')
->inject('dbForProject')
->inject('dbForPlatform')
->action(function (string $variableId, string $key, ?string $value, Document $project, Response $response, Database $dbForProject, Database $dbForPlatform) {
->action(function (string $variableId, string $key, ?string $value, ?bool $secret, Document $project, Response $response, Database $dbForProject, Database $dbForPlatform) {
$variable = $dbForProject->getDocument('variables', $variableId);
if ($variable === false || $variable->isEmpty() || $variable->getAttribute('resourceType') !== 'project') {
throw new Exception(Exception::VARIABLE_NOT_FOUND);
}
if ($variable->getAttribute('secret') === true && $secret === false) {
throw new Exception(Exception::VARIABLE_CANNOT_UNSET_SECRET);
}
$variable
->setAttribute('key', $key)
->setAttribute('value', $value ?? $variable->getAttribute('value'))
->setAttribute('secret', $secret ?? $variable->getAttribute('secret'))
->setAttribute('search', implode(' ', [$variableId, $key, 'project']));
try {
+1 -1
View File
@@ -130,7 +130,7 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
};
}
if ($type === 'function' || $type === 'site') {
if ($type === 'function' || $type === 'site' || $type === 'deployment') {
$method = $utopia->getRoute()?->getLabel('sdk', null);
if (empty($method)) {
Generated
+1 -1
View File
@@ -8780,5 +8780,5 @@
"platform-overrides": {
"php": "8.3"
},
"plugin-api-version": "2.3.0"
"plugin-api-version": "2.6.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.4
image: appwrite/console:5.3.0-sites-rc.5
restart: unless-stopped
networks:
- appwrite
+4
View File
@@ -118,6 +118,9 @@ class Exception extends \Exception
public const TEAM_INVITE_MISMATCH = 'team_invite_mismatch';
public const TEAM_ALREADY_EXISTS = 'team_already_exists';
/** Console */
public const RESOURCE_ALREADY_EXISTS = 'resource_already_exists';
/** Membership */
public const MEMBERSHIP_NOT_FOUND = 'membership_not_found';
public const MEMBERSHIP_ALREADY_CONFIRMED = 'membership_already_confirmed';
@@ -255,6 +258,7 @@ class Exception extends \Exception
/** Variables */
public const VARIABLE_NOT_FOUND = 'variable_not_found';
public const VARIABLE_ALREADY_EXISTS = 'variable_already_exists';
public const VARIABLE_CANNOT_UNSET_SECRET = 'variable_cannot_unset_secret';
/** Platform */
public const PLATFORM_NOT_FOUND = 'platform_not_found';
+2
View File
@@ -2,6 +2,7 @@
namespace Appwrite\Platform;
use Appwrite\Platform\Modules\Console;
use Appwrite\Platform\Modules\Core;
use Appwrite\Platform\Modules\Functions;
use Appwrite\Platform\Modules\Sites;
@@ -14,5 +15,6 @@ class Appwrite extends Platform
parent::__construct(new Core());
$this->addModule(new Functions\Module());
$this->addModule(new Sites\Module());
$this->addModule(new Console\Module());
}
}
@@ -0,0 +1,85 @@
<?php
namespace Appwrite\Platform\Modules\Console\Http\Resources;
use Appwrite\Extend\Exception;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Validator\Domain;
use Utopia\Validator\Text;
use Utopia\Validator\WhiteList;
class Get extends Action
{
use HTTP;
public static function getName()
{
return 'getResources';
}
public function __construct()
{
$this
->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET)
->setHttpPath('/v1/console/resources')
->desc('Check resource ID availability')
->groups(['api', 'projects'])
->label('scope', 'rules.read')
->label('sdk', new Method(
namespace: 'console',
name: 'getResource',
description: <<<EOT
Check if a resource ID is available.
EOT,
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_NOCONTENT,
model: Response::MODEL_NONE,
)
],
contentType: ContentType::NONE,
))
->label('abuse-limit', 10)
->label('abuse-key', 'userId:{userId}, url:{url}')
->label('abuse-time', 60)
->param('value', '', new Text(256), 'Resource value.')
->param('type', '', new WhiteList(['rules']), 'Resource type.')
->inject('response')
->inject('dbForPlatform')
->callback([$this, 'action']);
}
public function action(string $value, string $type, Response $response, Database $dbForPlatform)
{
if ($type === 'rules') {
$validator = new Domain($value);
if (!$validator->isValid($value)) {
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, $validator->getDescription());
}
$document = Authorization::skip(fn () => $dbForPlatform->findOne('rules', [
Query::equal('domain', [$value]),
]));
if (!$document->isEmpty()) {
throw new Exception(Exception::RESOURCE_ALREADY_EXISTS);
}
$response->noContent();
}
// Only occurs if type is added into whitelist, but not supported in action
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Invalid type');
}
}
@@ -0,0 +1,14 @@
<?php
namespace Appwrite\Platform\Modules\Console;
use Appwrite\Platform\Modules\Console\Services\Http;
use Utopia\Platform;
class Module extends Platform\Module
{
public function __construct()
{
$this->addService('http', new Http());
}
}
@@ -0,0 +1,16 @@
<?php
namespace Appwrite\Platform\Modules\Console\Services;
use Appwrite\Platform\Modules\Console\Http\Resources\Get as GetResourceAvailability;
use Utopia\Platform\Service;
class Http extends Service
{
public function __construct()
{
$this->type = Service::TYPE_HTTP;
// Resources
$this->addAction(GetResourceAvailability::getName(), new GetResourceAvailability());
}
}
@@ -56,14 +56,13 @@ class Create extends Base
->param('siteId', '', new UID(), 'Site unique ID.', false)
->param('key', null, new Text(Database::LENGTH_KEY), 'Variable key. Max length: ' . Database::LENGTH_KEY . ' chars.', false)
->param('value', null, new Text(8192, 0), 'Variable value. Max length: 8192 chars.', false)
->param('secret', false, new Boolean(), 'Is secret? Secret variables can only be updated or deleted, they cannot be read.', true)
->param('secret', true, new Boolean(), 'Secret variables can be updated or deleted, but only sites can read them during build and runtime.', true)
->inject('response')
->inject('dbForProject')
->inject('dbForPlatform')
->callback([$this, 'action']);
}
public function action(string $siteId, string $key, string $value, bool $secret, Response $response, Database $dbForProject, Database $dbForPlatform)
public function action(string $siteId, string $key, string $value, bool $secret, Response $response, Database $dbForProject)
{
$site = $dbForProject->getDocument('sites', $siteId);
@@ -13,6 +13,7 @@ use Utopia\Database\Exception\Duplicate as DuplicateException;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Validator\Boolean;
use Utopia\Validator\Text;
class Update extends Base
@@ -52,12 +53,13 @@ class Update extends Base
->param('variableId', '', new UID(), 'Variable unique ID.', false)
->param('key', null, new Text(255), 'Variable key. Max length: 255 chars.', false)
->param('value', null, new Text(8192, 0), 'Variable value. Max length: 8192 chars.', true)
->param('secret', null, new Boolean(), 'Secret variables can be updated or deleted, but only sites can read them during build and runtime.', true)
->inject('response')
->inject('dbForProject')
->callback([$this, 'action']);
}
public function action(string $siteId, string $variableId, string $key, ?string $value, Response $response, Database $dbForProject)
public function action(string $siteId, string $variableId, string $key, ?string $value, ?bool $secret, Response $response, Database $dbForProject)
{
$site = $dbForProject->getDocument('sites', $siteId);
@@ -74,9 +76,14 @@ class Update extends Base
throw new Exception(Exception::VARIABLE_NOT_FOUND);
}
if ($variable->getAttribute('secret') === true && $secret === false) {
throw new Exception(Exception::VARIABLE_CANNOT_UNSET_SECRET);
}
$variable
->setAttribute('key', $key)
->setAttribute('value', $value ?? $variable->getAttribute('value'))
->setAttribute('secret', $secret ?? $variable->getAttribute('secret'))
->setAttribute('search', implode(' ', [$variableId, $site->getId(), $key, 'site']));
try {
@@ -82,6 +82,46 @@ trait FunctionsBase
return $variable;
}
protected function getVariable(string $functionId, string $variableId): mixed
{
$variable = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/variables/' . $variableId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
return $variable;
}
protected function updateVariable(string $functionId, string $variableId, mixed $params): mixed
{
$variable = $this->client->call(Client::METHOD_PUT, '/functions/' . $functionId . '/variables/' . $variableId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), $params);
return $variable;
}
protected function listVariables(string $functionId, mixed $params = []): mixed
{
$variables = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/variables', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), $params);
return $variables;
}
protected function deleteVariable(string $functionId, string $variableId): mixed
{
$variable = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId . '/variables/' . $variableId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
return $variable;
}
protected function getFunction(string $functionId): mixed
{
$function = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId, array_merge([
@@ -110,7 +110,8 @@ class FunctionsConsoleClientTest extends Scope
$data['functionId'],
[
'key' => 'APP_TEST',
'value' => 'TESTINGVALUE'
'value' => 'TESTINGVALUE',
'secret' => false
]
);
@@ -131,6 +132,7 @@ class FunctionsConsoleClientTest extends Scope
$this->assertEquals(201, $variable['headers']['status-code']);
$this->assertEquals('APP_TEST_1', $variable['body']['key']);
$this->assertEmpty($variable['body']['value']);
$this->assertTrue($variable['body']['secret']);
$secretVariableId = $variable['body']['$id'];
@@ -142,7 +144,8 @@ class FunctionsConsoleClientTest extends Scope
$data['functionId'],
[
'key' => 'APP_TEST',
'value' => 'ANOTHERTESTINGVALUE'
'value' => 'ANOTHERTESTINGVALUE',
'secret' => false
]
);
@@ -320,6 +323,41 @@ class FunctionsConsoleClientTest extends Scope
$this->assertEquals("APP_TEST_UPDATE_2", $variable['body']['key']);
$this->assertEquals("TESTINGVALUEUPDATED", $variable['body']['value']);
// convert non-secret variable to secret
$response = $this->client->call(Client::METHOD_PUT, '/functions/' . $data['functionId'] . '/variables/' . $data['variableId'], array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'key' => 'APP_TEST_UPDATE_2',
'secret' => true
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals("APP_TEST_UPDATE_2", $response['body']['key']);
$this->assertEmpty($response['body']['value']);
$this->assertTrue($response['body']['secret']);
$variable = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/variables/' . $data['variableId'], array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$this->assertEquals(200, $variable['headers']['status-code']);
$this->assertEquals("APP_TEST_UPDATE_2", $variable['body']['key']);
$this->assertEmpty($variable['body']['value']);
$this->assertTrue($variable['body']['secret']);
// convert secret variable to non-secret
$response = $this->client->call(Client::METHOD_PUT, '/functions/' . $data['functionId'] . '/variables/' . $data['variableId'], array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'key' => 'APP_TEST_UPDATE',
'secret' => false
]);
$this->assertEquals(400, $response['headers']['status-code']);
/**
* Test for FAILURE
*/
@@ -410,4 +448,53 @@ class FunctionsConsoleClientTest extends Scope
return $data;
}
public function testVariableE2E(): void
{
$function = $this->createFunction([
'functionId' => ID::unique(),
'runtime' => 'node-18.0',
'name' => 'Variable E2E Test',
'entrypoint' => 'index.js',
'logging' => false,
'execute' => ['any']
]);
$this->assertEquals(201, $function['headers']['status-code']);
$this->assertFalse($function['body']['logging']);
$this->assertNotEmpty($function['body']['$id']);
$functionId = $function['body']['$id'] ?? '';
// create variable
$variable = $this->createVariable($functionId, [
'key' => 'CUSTOM_VARIABLE',
'value' => 'a_secret_value',
'secret' => true,
]);
$this->assertEquals(201, $variable['headers']['status-code']);
$this->assertNotEmpty($variable['body']['$id']);
$this->assertEquals('CUSTOM_VARIABLE', $variable['body']['key']);
$this->assertEquals('', $variable['body']['value']);
$this->assertEquals(true, $variable['body']['secret']);
$deploymentId = $this->setupDeployment($functionId, [
'entrypoint' => 'index.js',
'code' => $this->packageFunction('node'),
'activate' => true
]);
$this->assertNotEmpty($deploymentId);
$execution = $this->createExecution($functionId);
$this->assertEquals(201, $execution['headers']['status-code']);
$this->assertEmpty($execution['body']['logs']);
$this->assertEmpty($execution['body']['errors']);
$body = json_decode($execution['body']['responseBody']);
$this->assertEquals('a_secret_value', $body->CUSTOM_VARIABLE);
$this->cleanupFunction($functionId);
}
}
@@ -38,7 +38,7 @@ class FunctionsCustomServerTest extends Scope
'timeout' => 10,
]);
$functionId = $functionId = $function['body']['$id'] ?? '';
$functionId = $function['body']['$id'] ?? '';
$dateValidator = new DatetimeValidator();
$this->assertEquals(201, $function['headers']['status-code']);
@@ -356,7 +356,7 @@ class FunctionsCustomServerTest extends Scope
$this->assertEquals(201, $function['headers']['status-code']);
$this->assertNotEmpty($function['body']['$id']);
$functionId = $functionId = $function['body']['$id'] ?? '';
$functionId = $function['body']['$id'] ?? '';
$deployments = $this->listDeployments($functionId);
@@ -1898,7 +1898,7 @@ class FunctionsCustomServerTest extends Scope
$this->assertFalse($function['body']['logging']);
$this->assertNotEmpty($function['body']['$id']);
$functionId = $functionId = $function['body']['$id'] ?? '';
$functionId = $function['body']['$id'] ?? '';
$this->setupDeployment($functionId, [
'code' => $this->packageFunction('node'),
@@ -3923,10 +3923,14 @@ class ProjectsConsoleClientTest extends Scope
'x-appwrite-mode' => 'admin',
], $this->getHeaders()), [
'key' => 'APP_TEST',
'value' => 'TESTINGVALUE'
'value' => 'TESTINGVALUE',
'secret' => false
]);
$this->assertEquals(201, $variable['headers']['status-code']);
$this->assertEquals('APP_TEST', $variable['body']['key']);
$this->assertEquals('TESTINGVALUE', $variable['body']['value']);
$this->assertFalse($variable['body']['secret']);
$variableId = $variable['body']['$id'];
// test for secret variable
@@ -4047,6 +4051,7 @@ class ProjectsConsoleClientTest extends Scope
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals("APP_TEST_1", $response['body']['key']);
$this->assertEmpty($response['body']['value']);
$this->assertTrue($response['body']['secret']);
/**
* Test for FAILURE
@@ -4118,6 +4123,17 @@ class ProjectsConsoleClientTest extends Scope
$this->assertEquals("APP_TEST_UPDATE_1", $variable['body']['key']);
$this->assertEmpty($variable['body']['value']);
$response = $this->client->call(Client::METHOD_PUT, '/project/variables/' . $data['secretVariableId'], array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $data['projectId'],
'x-appwrite-mode' => 'admin',
], $this->getHeaders()), [
'key' => 'APP_TEST_UPDATE_1',
'secret' => false,
]);
$this->assertEquals(400, $response['headers']['status-code']);
$response = $this->client->call(Client::METHOD_GET, '/project/variables', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $data['projectId'],
+41 -1
View File
@@ -44,7 +44,7 @@ trait SitesBase
'x-appwrite-key' => $this->getProject()['apiKey'],
]));
$this->assertEquals('ready', $deployment['body']['status'], 'Deployment status is not ready, deployment: ' . json_encode($deployment['body'], JSON_PRETTY_PRINT));
}, 50000, 500);
}, 100000, 500);
return $deploymentId;
}
@@ -101,6 +101,46 @@ trait SitesBase
return $variable;
}
protected function getVariable(string $siteId, string $variableId): mixed
{
$variable = $this->client->call(Client::METHOD_GET, '/sites/' . $siteId . '/variables/' . $variableId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
return $variable;
}
protected function listVariables(string $siteId, mixed $params = []): mixed
{
$variables = $this->client->call(Client::METHOD_GET, '/sites/' . $siteId . '/variables', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), $params);
return $variables;
}
protected function updateVariable(string $siteId, string $variableId, mixed $params): mixed
{
$variable = $this->client->call(Client::METHOD_PUT, '/sites/' . $siteId . '/variables/' . $variableId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), $params);
return $variable;
}
protected function deleteVariable(string $siteId, string $variableId): mixed
{
$variable = $this->client->call(Client::METHOD_DELETE, '/sites/' . $siteId . '/variables/' . $variableId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
return $variable;
}
protected function getSite(string $siteId): mixed
{
$site = $this->client->call(Client::METHOD_GET, '/sites/' . $siteId, array_merge([
@@ -65,6 +65,264 @@ class SitesCustomServerTest extends Scope
$this->cleanupSite($siteId);
}
public function testConsoleAvailabilityEndpoint(): void
{
$siteId = $this->setupSite([
'siteId' => ID::unique(),
'name' => 'Test Site',
'framework' => 'other',
'buildRuntime' => 'ssr-22',
'outputDirectory' => './',
'subdomain' => 'test-site',
'fallbackFile' => null,
]);
$this->assertNotEmpty($siteId);
$rule = $this->getSiteDomain($siteId);
$response = $this->client->call(Client::METHOD_GET, '/console/resources', [
'origin' => 'http://localhost',
'content-type' => 'application/json',
'cookie' => 'a_session_console=' . $this->getRoot()['session'],
'x-appwrite-project' => 'console',
], [
'type' => 'rules',
'value' => $rule,
]);
$this->assertEquals(409, $response['headers']['status-code']); // domain unavailable
$nonExistingDomain = "non-existent-subdomain.sites.localhost";
$response = $this->client->call(Client::METHOD_GET, '/console/resources', [
'origin' => 'http://localhost',
'content-type' => 'application/json',
'cookie' => 'a_session_console=' . $this->getRoot()['session'],
'x-appwrite-project' => 'console',
], [
'type' => 'rules',
'value' => $nonExistingDomain,
]);
$this->assertEquals(204, $response['headers']['status-code']); // domain available
$this->cleanupSite($siteId);
$this->assertEventually(function () use ($siteId) {
$rule = $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', [$siteId])
]
]);
$this->assertEquals(200, $rule['headers']['status-code']);
$this->assertEquals(0, $rule['body']['total']);
}, 5000, 500);
$response = $this->client->call(Client::METHOD_GET, '/console/resources', [
'origin' => 'http://localhost',
'content-type' => 'application/json',
'cookie' => 'a_session_console=' . $this->getRoot()['session'],
'x-appwrite-project' => 'console',
], [
'type' => 'rules',
'value' => $rule,
]);
$this->assertEquals(204, $response['headers']['status-code']); // domain available as site is deleted
}
public function testVariables(): void
{
$site = $this->createSite([
'buildRuntime' => 'ssr-22',
'fallbackFile' => null,
'framework' => 'other',
'name' => 'Test Site',
'outputDirectory' => './',
'siteId' => ID::unique()
]);
$siteId = $site['body']['$id'] ?? '';
$this->assertEquals(201, $site['headers']['status-code']);
$this->assertNotEmpty($site['body']['$id']);
$this->assertEquals('Test Site', $site['body']['name']);
$variable = $this->createVariable($siteId, [
'key' => 'siteKey1',
'value' => 'siteValue1',
'secret' => false,
]);
$this->assertEquals(201, $variable['headers']['status-code']);
$this->assertNotEmpty($variable['body']['$id']);
$this->assertEquals('siteKey1', $variable['body']['key']);
$this->assertEquals('siteValue1', $variable['body']['value']);
$this->assertEquals(false, $variable['body']['secret']);
$variable2 = $this->createVariable($siteId, [
'key' => 'siteKey2',
'value' => 'siteValue2',
'secret' => false,
]);
$this->assertEquals(201, $variable2['headers']['status-code']);
$this->assertNotEmpty($variable2['body']['$id']);
$this->assertEquals('siteKey2', $variable2['body']['key']);
$this->assertEquals('siteValue2', $variable2['body']['value']);
$this->assertEquals(false, $variable2['body']['secret']);
$secretVariable = $this->createVariable($siteId, [
'key' => 'siteKey3',
'value' => 'siteValue3',
'secret' => true,
]);
$this->assertEquals(201, $secretVariable['headers']['status-code']);
$this->assertNotEmpty($secretVariable['body']['$id']);
$this->assertEquals('siteKey3', $secretVariable['body']['key']);
$this->assertEquals('', $secretVariable['body']['value']);
$this->assertEquals(true, $secretVariable['body']['secret']);
$variable = $this->getVariable($siteId, $variable['body']['$id']);
$this->assertEquals(200, $variable['headers']['status-code']);
$this->assertNotEmpty($variable['body']['$id']);
$this->assertEquals('siteKey1', $variable['body']['key']);
$this->assertEquals('siteValue1', $variable['body']['value']);
$this->assertEquals(false, $variable['body']['secret']);
$secretVariable = $this->getVariable($siteId, $secretVariable['body']['$id']);
$this->assertEquals(200, $secretVariable['headers']['status-code']);
$this->assertNotEmpty($secretVariable['body']['$id']);
$this->assertEquals('siteKey3', $secretVariable['body']['key']);
$this->assertEquals('', $secretVariable['body']['value']);
$this->assertEquals(true, $secretVariable['body']['secret']);
$variable = $this->updateVariable($siteId, $variable['body']['$id'], [
'key' => 'siteKey1Updated',
'value' => 'siteValue1Updated',
]);
$this->assertEquals(200, $variable['headers']['status-code']);
$this->assertNotEmpty($variable['body']['$id']);
$this->assertEquals('siteKey1Updated', $variable['body']['key']);
$this->assertEquals('siteValue1Updated', $variable['body']['value']);
$this->assertEquals(false, $variable['body']['secret']);
$variable = $this->updateVariable($siteId, $variable['body']['$id'], [
'key' => 'siteKey1Updated',
'secret' => true,
]);
$this->assertEquals(200, $variable['headers']['status-code']);
$this->assertNotEmpty($variable['body']['$id']);
$this->assertEquals('siteKey1Updated', $variable['body']['key']);
$this->assertEquals('', $variable['body']['value']);
$this->assertEquals(true, $variable['body']['secret']);
$secretVariable = $this->updateVariable($siteId, $secretVariable['body']['$id'], [
'key' => 'siteKey3',
'value' => 'siteValue3Updated',
]);
$this->assertEquals(200, $secretVariable['headers']['status-code']);
$this->assertNotEmpty($secretVariable['body']['$id']);
$this->assertEquals('siteKey3', $secretVariable['body']['key']);
$this->assertEquals('', $secretVariable['body']['value']);
$this->assertEquals(true, $secretVariable['body']['secret']);
$response = $this->updateVariable($siteId, $secretVariable['body']['$id'], [
'key' => 'siteKey3',
'secret' => false,
]);
$this->assertEquals(400, $response['headers']['status-code']);
$secretVariable = $this->getVariable($siteId, $secretVariable['body']['$id']);
$this->assertEquals(200, $secretVariable['headers']['status-code']);
$this->assertNotEmpty($secretVariable['body']['$id']);
$this->assertEquals('siteKey3', $secretVariable['body']['key']);
$this->assertEquals('', $secretVariable['body']['value']);
$this->assertEquals(true, $secretVariable['body']['secret']);
$variables = $this->listVariables($siteId);
$this->assertEquals(200, $variables['headers']['status-code']);
$this->assertCount(3, $variables['body']['variables']);
$response = $this->deleteVariable($siteId, $variable['body']['$id']);
$this->assertEquals(204, $response['headers']['status-code']);
$response = $this->deleteVariable($siteId, $variable2['body']['$id']);
$this->assertEquals(204, $response['headers']['status-code']);
$response = $this->deleteVariable($siteId, $secretVariable['body']['$id']);
$this->assertEquals(204, $response['headers']['status-code']);
$variables = $this->listVariables($siteId);
$this->assertEquals(200, $variables['headers']['status-code']);
$this->assertCount(0, $variables['body']['variables']);
$this->cleanupSite($siteId);
}
public function testVariablesE2E(): void
{
$siteId = $this->setupSite([
'siteId' => ID::unique(),
'name' => 'Astro site',
'framework' => 'astro',
'adapter' => 'ssr',
'buildRuntime' => 'ssr-22',
'outputDirectory' => './dist',
'buildCommand' => 'npm run build',
'installCommand' => 'npm install',
'fallbackFile' => '',
]);
$this->assertNotEmpty($siteId);
$secretVariable = $this->createVariable($siteId, [
'key' => 'name',
'value' => 'Appwrite',
]);
$this->assertEquals(201, $secretVariable['headers']['status-code']);
$this->assertNotEmpty($secretVariable['body']['$id']);
$this->assertEquals('name', $secretVariable['body']['key']);
$this->assertEquals('', $secretVariable['body']['value']);
$this->assertEquals(true, $secretVariable['body']['secret']);
$deploymentId = $this->setupDeployment($siteId, [
'code' => $this->packageSite('astro'),
'activate' => 'true'
]);
$this->assertNotEmpty($deploymentId);
$domain = $this->getSiteDomain($siteId);
$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("Env variable is Appwrite", $response['body']);
$this->assertStringNotContainsString("Variable not found", $response['body']);
$this->cleanupSite($siteId);
}
public function testListSites(): void
{
/**
@@ -0,0 +1,10 @@
import { defineConfig } from 'astro/config';
import node from '@astrojs/node';
import 'dotenv/config';
export default defineConfig({
output: 'server',
adapter: node({
mode: 'standalone'
}),
});
+16
View File
@@ -0,0 +1,16 @@
{
"name": "my-astro-app",
"type": "module",
"version": "0.0.1",
"scripts": {
"dev": "astro dev",
"build": "astro build",
"preview": "astro preview",
"astro": "astro"
},
"dependencies": {
"@astrojs/node": "^9.0.2",
"astro": "^5.2.5",
"dotenv": "^16.4.7"
}
}
@@ -0,0 +1,15 @@
---
const value = import.meta.env.name || 'Variable not found';
---
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Astro SSR</title>
</head>
<body>
<p>Env variable is {value}</p>
</body>
</html>
@@ -0,0 +1,5 @@
{
"extends": "astro/tsconfigs/strict",
"include": [".astro/types.d.ts", "**/*"],
"exclude": ["dist"]
}