Merge branch 'feat-sites' into feat-update-error-pages
|
|
@ -1417,6 +1417,13 @@ return [
|
|||
'lengths' => [],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_roles'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['roles'],
|
||||
'lengths' => [128],
|
||||
'orders' => [],
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
|
|
|
|||
9612
app/config/specs/open-api3-1.7.x-client.json
Normal file
41627
app/config/specs/open-api3-1.7.x-console.json
Normal file
29170
app/config/specs/open-api3-1.7.x-server.json
Normal file
|
|
@ -6848,7 +6848,7 @@
|
|||
},
|
||||
{
|
||||
"name": "queries",
|
||||
"description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: userId, teamId, invited, joined, confirm",
|
||||
"description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: userId, teamId, invited, joined, confirm, roles",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "array",
|
||||
|
|
|
|||
|
|
@ -9362,7 +9362,7 @@
|
|||
"rate-limit": 0,
|
||||
"rate-time": 3600,
|
||||
"rate-key": "url:{url},ip:{ip}",
|
||||
"scope": "functions.read",
|
||||
"scope": "public",
|
||||
"platforms": [
|
||||
"server"
|
||||
],
|
||||
|
|
@ -24918,7 +24918,7 @@
|
|||
"rate-limit": 0,
|
||||
"rate-time": 3600,
|
||||
"rate-key": "url:{url},ip:{ip}",
|
||||
"scope": "sites.read",
|
||||
"scope": "public",
|
||||
"platforms": [
|
||||
"server"
|
||||
],
|
||||
|
|
@ -28895,7 +28895,7 @@
|
|||
},
|
||||
{
|
||||
"name": "queries",
|
||||
"description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: userId, teamId, invited, joined, confirm",
|
||||
"description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: userId, teamId, invited, joined, confirm, roles",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "array",
|
||||
|
|
@ -30988,6 +30988,30 @@
|
|||
"x-example": "<USER_ID>"
|
||||
},
|
||||
"in": "path"
|
||||
},
|
||||
{
|
||||
"name": "queries",
|
||||
"description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: userId, teamId, invited, joined, confirm, roles",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": []
|
||||
},
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"name": "search",
|
||||
"description": "Search term to filter your list results. Max length: 256 chars.",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"x-example": "<SEARCH>",
|
||||
"default": ""
|
||||
},
|
||||
"in": "query"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -40890,6 +40914,11 @@
|
|||
"description": "A domain to use for site URLs.",
|
||||
"x-example": "sites.localhost"
|
||||
},
|
||||
"_APP_DOMAIN_FUNCTIONS": {
|
||||
"type": "string",
|
||||
"description": "A domain to use for function URLs.",
|
||||
"x-example": "functions.localhost"
|
||||
},
|
||||
"_APP_OPTIONS_FORCE_HTTPS": {
|
||||
"type": "string",
|
||||
"description": "Defines if HTTPS is enforced for all requests.",
|
||||
|
|
@ -40910,6 +40939,7 @@
|
|||
"_APP_DOMAIN_ENABLED",
|
||||
"_APP_ASSISTANT_ENABLED",
|
||||
"_APP_DOMAIN_SITES",
|
||||
"_APP_DOMAIN_FUNCTIONS",
|
||||
"_APP_OPTIONS_FORCE_HTTPS",
|
||||
"_APP_DOMAINS_NAMESERVERS"
|
||||
]
|
||||
|
|
|
|||
|
|
@ -8449,7 +8449,7 @@
|
|||
"rate-limit": 0,
|
||||
"rate-time": 3600,
|
||||
"rate-key": "url:{url},ip:{ip}",
|
||||
"scope": "functions.read",
|
||||
"scope": "public",
|
||||
"platforms": [
|
||||
"server"
|
||||
],
|
||||
|
|
@ -17016,7 +17016,7 @@
|
|||
"rate-limit": 0,
|
||||
"rate-time": 3600,
|
||||
"rate-key": "url:{url},ip:{ip}",
|
||||
"scope": "sites.read",
|
||||
"scope": "public",
|
||||
"platforms": [
|
||||
"server"
|
||||
],
|
||||
|
|
@ -20515,7 +20515,7 @@
|
|||
},
|
||||
{
|
||||
"name": "queries",
|
||||
"description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: userId, teamId, invited, joined, confirm",
|
||||
"description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: userId, teamId, invited, joined, confirm, roles",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "array",
|
||||
|
|
@ -22569,6 +22569,30 @@
|
|||
"x-example": "<USER_ID>"
|
||||
},
|
||||
"in": "path"
|
||||
},
|
||||
{
|
||||
"name": "queries",
|
||||
"description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: userId, teamId, invited, joined, confirm, roles",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": []
|
||||
},
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"name": "search",
|
||||
"description": "Search term to filter your list results. Max length: 256 chars.",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"x-example": "<SEARCH>",
|
||||
"default": ""
|
||||
},
|
||||
"in": "query"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
9771
app/config/specs/swagger2-1.7.x-client.json
Normal file
42246
app/config/specs/swagger2-1.7.x-console.json
Normal file
29680
app/config/specs/swagger2-1.7.x-server.json
Normal file
|
|
@ -7060,7 +7060,7 @@
|
|||
},
|
||||
{
|
||||
"name": "queries",
|
||||
"description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: userId, teamId, invited, joined, confirm",
|
||||
"description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: userId, teamId, invited, joined, confirm, roles",
|
||||
"required": false,
|
||||
"type": "array",
|
||||
"collectionFormat": "multi",
|
||||
|
|
|
|||
|
|
@ -9530,7 +9530,7 @@
|
|||
"rate-limit": 0,
|
||||
"rate-time": 3600,
|
||||
"rate-key": "url:{url},ip:{ip}",
|
||||
"scope": "functions.read",
|
||||
"scope": "public",
|
||||
"platforms": [
|
||||
"server"
|
||||
],
|
||||
|
|
@ -25437,7 +25437,7 @@
|
|||
"rate-limit": 0,
|
||||
"rate-time": 3600,
|
||||
"rate-key": "url:{url},ip:{ip}",
|
||||
"scope": "sites.read",
|
||||
"scope": "public",
|
||||
"platforms": [
|
||||
"server"
|
||||
],
|
||||
|
|
@ -29428,7 +29428,7 @@
|
|||
},
|
||||
{
|
||||
"name": "queries",
|
||||
"description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: userId, teamId, invited, joined, confirm",
|
||||
"description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: userId, teamId, invited, joined, confirm, roles",
|
||||
"required": false,
|
||||
"type": "array",
|
||||
"collectionFormat": "multi",
|
||||
|
|
@ -31571,6 +31571,27 @@
|
|||
"type": "string",
|
||||
"x-example": "<USER_ID>",
|
||||
"in": "path"
|
||||
},
|
||||
{
|
||||
"name": "queries",
|
||||
"description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: userId, teamId, invited, joined, confirm, roles",
|
||||
"required": false,
|
||||
"type": "array",
|
||||
"collectionFormat": "multi",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": [],
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"name": "search",
|
||||
"description": "Search term to filter your list results. Max length: 256 chars.",
|
||||
"required": false,
|
||||
"type": "string",
|
||||
"x-example": "<SEARCH>",
|
||||
"default": "",
|
||||
"in": "query"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -41552,6 +41573,11 @@
|
|||
"description": "A domain to use for site URLs.",
|
||||
"x-example": "sites.localhost"
|
||||
},
|
||||
"_APP_DOMAIN_FUNCTIONS": {
|
||||
"type": "string",
|
||||
"description": "A domain to use for function URLs.",
|
||||
"x-example": "functions.localhost"
|
||||
},
|
||||
"_APP_OPTIONS_FORCE_HTTPS": {
|
||||
"type": "string",
|
||||
"description": "Defines if HTTPS is enforced for all requests.",
|
||||
|
|
@ -41572,6 +41598,7 @@
|
|||
"_APP_DOMAIN_ENABLED",
|
||||
"_APP_ASSISTANT_ENABLED",
|
||||
"_APP_DOMAIN_SITES",
|
||||
"_APP_DOMAIN_FUNCTIONS",
|
||||
"_APP_OPTIONS_FORCE_HTTPS",
|
||||
"_APP_DOMAINS_NAMESERVERS"
|
||||
]
|
||||
|
|
|
|||
|
|
@ -8614,7 +8614,7 @@
|
|||
"rate-limit": 0,
|
||||
"rate-time": 3600,
|
||||
"rate-key": "url:{url},ip:{ip}",
|
||||
"scope": "functions.read",
|
||||
"scope": "public",
|
||||
"platforms": [
|
||||
"server"
|
||||
],
|
||||
|
|
@ -17501,7 +17501,7 @@
|
|||
"rate-limit": 0,
|
||||
"rate-time": 3600,
|
||||
"rate-key": "url:{url},ip:{ip}",
|
||||
"scope": "sites.read",
|
||||
"scope": "public",
|
||||
"platforms": [
|
||||
"server"
|
||||
],
|
||||
|
|
@ -21023,7 +21023,7 @@
|
|||
},
|
||||
{
|
||||
"name": "queries",
|
||||
"description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: userId, teamId, invited, joined, confirm",
|
||||
"description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: userId, teamId, invited, joined, confirm, roles",
|
||||
"required": false,
|
||||
"type": "array",
|
||||
"collectionFormat": "multi",
|
||||
|
|
@ -23127,6 +23127,27 @@
|
|||
"type": "string",
|
||||
"x-example": "<USER_ID>",
|
||||
"in": "path"
|
||||
},
|
||||
{
|
||||
"name": "queries",
|
||||
"description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: userId, teamId, invited, joined, confirm, roles",
|
||||
"required": false,
|
||||
"type": "array",
|
||||
"collectionFormat": "multi",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": [],
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"name": "search",
|
||||
"description": "Search term to filter your list results. Max length: 256 chars.",
|
||||
"required": false,
|
||||
"type": "string",
|
||||
"x-example": "<SEARCH>",
|
||||
"default": "",
|
||||
"in": "query"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -496,6 +496,7 @@ App::get('/v1/avatars/qr')
|
|||
'addQuietzone' => true,
|
||||
'quietzoneSize' => $margin,
|
||||
'outputType' => QRCode::OUTPUT_IMAGICK,
|
||||
'scale' => 15,
|
||||
]);
|
||||
|
||||
$qrcode = new QRCode($options);
|
||||
|
|
@ -510,7 +511,7 @@ App::get('/v1/avatars/qr')
|
|||
$response
|
||||
->addHeader('Cache-Control', 'private, max-age=3888000') // 45 days
|
||||
->setContentType('image/png')
|
||||
->send($image->output('png', 9));
|
||||
->send($image->output('png', 90));
|
||||
});
|
||||
|
||||
App::get('/v1/avatars/initials')
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ App::get('/v1/console/variables')
|
|||
'_APP_DOMAIN_ENABLED' => $isDomainEnabled,
|
||||
'_APP_ASSISTANT_ENABLED' => $isAssistantEnabled,
|
||||
'_APP_DOMAIN_SITES' => System::getEnv('_APP_DOMAIN_SITES'),
|
||||
'_APP_DOMAIN_FUNCTIONS' => System::getEnv('_APP_DOMAIN_FUNCTIONS'),
|
||||
'_APP_OPTIONS_FORCE_HTTPS' => System::getEnv('_APP_OPTIONS_FORCE_HTTPS'),
|
||||
'_APP_DOMAINS_NAMESERVERS' => System::getEnv('_APP_DOMAINS_NAMESERVERS'),
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ use Appwrite\SDK\Method;
|
|||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Database\Validator\CustomId;
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Identities;
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Memberships;
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Targets;
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Users;
|
||||
use Appwrite\Utopia\Request;
|
||||
|
|
@ -799,9 +800,11 @@ App::get('/v1/users/:userId/memberships')
|
|||
]
|
||||
))
|
||||
->param('userId', '', new UID(), 'User ID.')
|
||||
->param('queries', [], new Memberships(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Memberships::ALLOWED_ATTRIBUTES), true)
|
||||
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->action(function (string $userId, Response $response, Database $dbForProject) {
|
||||
->action(function (string $userId, array $queries, string $search, Response $response, Database $dbForProject) {
|
||||
|
||||
$user = $dbForProject->getDocument('users', $userId);
|
||||
|
||||
|
|
@ -809,6 +812,19 @@ App::get('/v1/users/:userId/memberships')
|
|||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
if (!empty($search)) {
|
||||
$queries[] = Query::search('search', $search);
|
||||
}
|
||||
|
||||
// Set internal queries
|
||||
$queries[] = Query::equal('userInternalId', [$user->getInternalId()]);
|
||||
|
||||
$memberships = array_map(function ($membership) use ($dbForProject, $user) {
|
||||
$team = $dbForProject->getDocument('teams', $membership->getAttribute('teamId'));
|
||||
|
||||
|
|
@ -818,7 +834,7 @@ App::get('/v1/users/:userId/memberships')
|
|||
->setAttribute('userEmail', $user->getAttribute('email'));
|
||||
|
||||
return $membership;
|
||||
}, $user->getAttribute('memberships', []));
|
||||
}, $dbForProject->find('memberships', $queries));
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'memberships' => $memberships,
|
||||
|
|
|
|||
|
|
@ -134,12 +134,12 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
|
|||
|
||||
$comment = new Comment();
|
||||
$comment->parseComment($github->getComment($owner, $repositoryName, $latestCommentId));
|
||||
$comment->addBuild($project, $resource, $resourceType, $commentStatus, $deploymentId, $action, '', '');
|
||||
$comment->addBuild($project, $resource, $resourceType, $commentStatus, $deploymentId, $action, '');
|
||||
|
||||
$latestCommentId = \strval($github->updateComment($owner, $repositoryName, $latestCommentId, $comment->generateComment()));
|
||||
} else {
|
||||
$comment = new Comment();
|
||||
$comment->addBuild($project, $resource, $resourceType, $commentStatus, $deploymentId, $action, '', '');
|
||||
$comment->addBuild($project, $resource, $resourceType, $commentStatus, $deploymentId, $action, '');
|
||||
$latestCommentId = \strval($github->createComment($owner, $repositoryName, $providerPullRequestId, $comment->generateComment()));
|
||||
|
||||
if (!empty($latestCommentId)) {
|
||||
|
|
@ -176,7 +176,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
|
|||
$latestCommentId = $comment->getAttribute('providerCommentId', '');
|
||||
$comment = new Comment();
|
||||
$comment->parseComment($github->getComment($owner, $repositoryName, $latestCommentId));
|
||||
$comment->addBuild($project, $resource, $resourceType, $commentStatus, $deploymentId, $action, '', '');
|
||||
$comment->addBuild($project, $resource, $resourceType, $commentStatus, $deploymentId, $action, '');
|
||||
|
||||
$latestCommentId = \strval($github->updateComment($owner, $repositoryName, $latestCommentId, $comment->generateComment()));
|
||||
}
|
||||
|
|
@ -219,7 +219,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
|
|||
$commands[] = $resource->getAttribute('commands', '');
|
||||
}
|
||||
|
||||
$deployment = $dbForProject->createDocument('deployments', new Document([
|
||||
$deployment = Authorization::skip(fn () => $dbForProject->createDocument('deployments', new Document([
|
||||
'$id' => $deploymentId,
|
||||
'$permissions' => [
|
||||
Permission::read(Role::any()),
|
||||
|
|
@ -253,14 +253,14 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
|
|||
'providerBranch' => $providerBranch,
|
||||
'search' => implode(' ', [$deploymentId, $resource->getAttribute('entrypoint', '')]),
|
||||
'activate' => $activate,
|
||||
]));
|
||||
])));
|
||||
|
||||
$resource = $resource
|
||||
->setAttribute('latestDeploymentId', $deployment->getId())
|
||||
->setAttribute('latestDeploymentInternalId', $deployment->getInternalId())
|
||||
->setAttribute('latestDeploymentCreatedAt', $deployment->getCreatedAt())
|
||||
->setAttribute('latestDeploymentStatus', $deployment->getAttribute('status', ''));
|
||||
$dbForProject->updateDocument($resource->getCollection(), $resource->getId(), $resource);
|
||||
Authorization::skip(fn () => $dbForProject->updateDocument($resource->getCollection(), $resource->getId(), $resource));
|
||||
|
||||
if ($resource->getCollection() === 'sites') {
|
||||
$projectId = $project->getId();
|
||||
|
|
|
|||
|
|
@ -478,24 +478,28 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
|
|||
);
|
||||
|
||||
// Branded 404 override
|
||||
$isResponseBranded = false;
|
||||
if ($executionResponse['statusCode'] === 404 && $deployment->getAttribute('adapter', '') === 'static') {
|
||||
$layout = new View(__DIR__ . '/../views/general/404.phtml');
|
||||
$executionResponse['body'] = $layout->render();
|
||||
$executionResponse['headers']['content-length'] = \strlen($executionResponse['body']);
|
||||
$isResponseBranded = true;
|
||||
}
|
||||
|
||||
// Branded banner for previews
|
||||
if (\is_null($apiKey) || $apiKey->isBannerDisabled() === false) {
|
||||
$transformation = new Transformation();
|
||||
$transformation->addAdapter(new Preview());
|
||||
$transformation->setInput($executionResponse['body']);
|
||||
$transformation->setTraits($executionResponse['headers']);
|
||||
if ($isPreview && $transformation->transform()) {
|
||||
$executionResponse['body'] = $transformation->getOutput();
|
||||
if (!$isResponseBranded) {
|
||||
if (\is_null($apiKey) || $apiKey->isBannerDisabled() === false) {
|
||||
$transformation = new Transformation();
|
||||
$transformation->addAdapter(new Preview());
|
||||
$transformation->setInput($executionResponse['body']);
|
||||
$transformation->setTraits($executionResponse['headers']);
|
||||
if ($isPreview && $transformation->transform()) {
|
||||
$executionResponse['body'] = $transformation->getOutput();
|
||||
|
||||
foreach ($executionResponse['headers'] as $key => $value) {
|
||||
if (\strtolower($key) === 'content-length') {
|
||||
$executionResponse['headers'][$key] = \strlen($executionResponse['body']);
|
||||
foreach ($executionResponse['headers'] as $key => $value) {
|
||||
if (\strtolower($key) === 'content-length') {
|
||||
$executionResponse['headers'][$key] = \strlen($executionResponse['body']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
42
composer.lock
generated
|
|
@ -4205,16 +4205,16 @@
|
|||
},
|
||||
{
|
||||
"name": "utopia-php/migration",
|
||||
"version": "0.8.1",
|
||||
"version": "0.8.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/migration.git",
|
||||
"reference": "36ec7af2e6bf78de5d86e1b0a953fd7dcdf69dab"
|
||||
"reference": "845fd04ccf5e0edb03c184b864e0596080a432b8"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/migration/zipball/36ec7af2e6bf78de5d86e1b0a953fd7dcdf69dab",
|
||||
"reference": "36ec7af2e6bf78de5d86e1b0a953fd7dcdf69dab",
|
||||
"url": "https://api.github.com/repos/utopia-php/migration/zipball/845fd04ccf5e0edb03c184b864e0596080a432b8",
|
||||
"reference": "845fd04ccf5e0edb03c184b864e0596080a432b8",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -4222,7 +4222,7 @@
|
|||
"ext-curl": "*",
|
||||
"ext-openssl": "*",
|
||||
"php": ">=8.1",
|
||||
"utopia-php/database": "0.61.*",
|
||||
"utopia-php/database": "0.*.*",
|
||||
"utopia-php/dsn": "0.2.*",
|
||||
"utopia-php/framework": "0.33.*",
|
||||
"utopia-php/storage": "0.18.*"
|
||||
|
|
@ -4255,9 +4255,9 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/migration/issues",
|
||||
"source": "https://github.com/utopia-php/migration/tree/0.8.1"
|
||||
"source": "https://github.com/utopia-php/migration/tree/0.8.4"
|
||||
},
|
||||
"time": "2025-03-18T07:48:08+00:00"
|
||||
"time": "2025-03-28T02:08:22+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/mongo",
|
||||
|
|
@ -7527,16 +7527,16 @@
|
|||
},
|
||||
{
|
||||
"name": "symfony/console",
|
||||
"version": "v7.2.1",
|
||||
"version": "v7.2.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/console.git",
|
||||
"reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3"
|
||||
"reference": "e51498ea18570c062e7df29d05a7003585b19b88"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/console/zipball/fefcc18c0f5d0efe3ab3152f15857298868dc2c3",
|
||||
"reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3",
|
||||
"url": "https://api.github.com/repos/symfony/console/zipball/e51498ea18570c062e7df29d05a7003585b19b88",
|
||||
"reference": "e51498ea18570c062e7df29d05a7003585b19b88",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -7600,7 +7600,7 @@
|
|||
"terminal"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/console/tree/v7.2.1"
|
||||
"source": "https://github.com/symfony/console/tree/v7.2.5"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
|
@ -7616,7 +7616,7 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-12-11T03:49:26+00:00"
|
||||
"time": "2025-03-12T08:11:12+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/filesystem",
|
||||
|
|
@ -8131,16 +8131,16 @@
|
|||
},
|
||||
{
|
||||
"name": "symfony/process",
|
||||
"version": "v7.2.4",
|
||||
"version": "v7.2.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/process.git",
|
||||
"reference": "d8f411ff3c7ddc4ae9166fb388d1190a2df5b5cf"
|
||||
"reference": "87b7c93e57df9d8e39a093d32587702380ff045d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/process/zipball/d8f411ff3c7ddc4ae9166fb388d1190a2df5b5cf",
|
||||
"reference": "d8f411ff3c7ddc4ae9166fb388d1190a2df5b5cf",
|
||||
"url": "https://api.github.com/repos/symfony/process/zipball/87b7c93e57df9d8e39a093d32587702380ff045d",
|
||||
"reference": "87b7c93e57df9d8e39a093d32587702380ff045d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -8172,7 +8172,7 @@
|
|||
"description": "Executes commands in sub-processes",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/process/tree/v7.2.4"
|
||||
"source": "https://github.com/symfony/process/tree/v7.2.5"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
|
@ -8188,7 +8188,7 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-02-05T08:33:46+00:00"
|
||||
"time": "2025-03-13T12:21:46+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/string",
|
||||
|
|
@ -8507,7 +8507,7 @@
|
|||
],
|
||||
"aliases": [],
|
||||
"minimum-stability": "stable",
|
||||
"stability-flags": {},
|
||||
"stability-flags": [],
|
||||
"prefer-stable": false,
|
||||
"prefer-lowest": false,
|
||||
"platform": {
|
||||
|
|
@ -8531,5 +8531,5 @@
|
|||
"platform-overrides": {
|
||||
"php": "8.3"
|
||||
},
|
||||
"plugin-api-version": "2.6.0"
|
||||
"plugin-api-version": "2.3.0"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -205,7 +205,7 @@ services:
|
|||
appwrite-console:
|
||||
<<: *x-logging
|
||||
container_name: appwrite-console
|
||||
image: appwrite/console:5.3.0-sites-rc.31
|
||||
image: appwrite/console:5.3.0-sites-rc.35
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- appwrite
|
||||
|
|
|
|||
18
public/images/vcs/qr-dark.svg
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<mask id="path-1-outside-1_6367_338401" maskUnits="userSpaceOnUse" x="0" y="0" width="30" height="30" fill="black">
|
||||
<rect fill="white" width="30" height="30"/>
|
||||
<path d="M1 10.6C1 7.23969 1 5.55953 1.65396 4.27606C2.2292 3.14708 3.14708 2.2292 4.27606 1.65396C5.55953 1 7.23969 1 10.6 1H19.4C22.7603 1 24.4405 1 25.7239 1.65396C26.8529 2.2292 27.7708 3.14708 28.346 4.27606C29 5.55953 29 7.23969 29 10.6V19.4C29 22.7603 29 24.4405 28.346 25.7239C27.7708 26.8529 26.8529 27.7708 25.7239 28.346C24.4405 29 22.7603 29 19.4 29H10.6C7.23969 29 5.55953 29 4.27606 28.346C3.14708 27.7708 2.2292 26.8529 1.65396 25.7239C1 24.4405 1 22.7603 1 19.4V10.6Z"/>
|
||||
</mask>
|
||||
<path d="M1 10.6C1 7.23969 1 5.55953 1.65396 4.27606C2.2292 3.14708 3.14708 2.2292 4.27606 1.65396C5.55953 1 7.23969 1 10.6 1H19.4C22.7603 1 24.4405 1 25.7239 1.65396C26.8529 2.2292 27.7708 3.14708 28.346 4.27606C29 5.55953 29 7.23969 29 10.6V19.4C29 22.7603 29 24.4405 28.346 25.7239C27.7708 26.8529 26.8529 27.7708 25.7239 28.346C24.4405 29 22.7603 29 19.4 29H10.6C7.23969 29 5.55953 29 4.27606 28.346C3.14708 27.7708 2.2292 26.8529 1.65396 25.7239C1 24.4405 1 22.7603 1 19.4V10.6Z" fill="#1D1D21"/>
|
||||
<path d="M4.27606 28.346L4.73005 27.455L4.27606 28.346ZM1.65396 25.7239L0.762954 26.1779L1.65396 25.7239ZM28.346 25.7239L29.237 26.1779L28.346 25.7239ZM25.7239 28.346L26.1779 29.237L25.7239 28.346ZM28.346 4.27606L27.455 4.73005L28.346 4.27606ZM10.6 2H19.4V0H10.6V2ZM28 10.6V19.4H30V10.6H28ZM19.4 28H10.6V30H19.4V28ZM2 19.4V10.6H0V19.4H2ZM10.6 28C8.90334 28 7.69099 27.9992 6.74064 27.9216C5.80197 27.8449 5.20731 27.6982 4.73005 27.455L3.82207 29.237C4.62827 29.6478 5.51543 29.8281 6.57778 29.9149C7.62846 30.0008 8.93634 30 10.6 30V28ZM0 19.4C0 21.0637 -0.000777706 22.3715 0.0850662 23.4222C0.171864 24.4846 0.352173 25.3717 0.762954 26.1779L2.54497 25.27C2.30179 24.7927 2.15512 24.198 2.07842 23.2594C2.00078 22.309 2 21.0967 2 19.4H0ZM4.73005 27.455C3.78924 26.9757 3.02433 26.2108 2.54497 25.27L0.762954 26.1779C1.43407 27.4951 2.50493 28.5659 3.82207 29.237L4.73005 27.455ZM28 19.4C28 21.0967 27.9992 22.309 27.9216 23.2594C27.8449 24.198 27.6982 24.7927 27.455 25.27L29.237 26.1779C29.6478 25.3717 29.8281 24.4846 29.9149 23.4222C30.0008 22.3715 30 21.0637 30 19.4H28ZM19.4 30C21.0637 30 22.3715 30.0008 23.4222 29.9149C24.4846 29.8281 25.3717 29.6478 26.1779 29.237L25.27 27.455C24.7927 27.6982 24.198 27.8449 23.2594 27.9216C22.309 27.9992 21.0967 28 19.4 28V30ZM27.455 25.27C26.9757 26.2108 26.2108 26.9757 25.27 27.455L26.1779 29.237C27.4951 28.5659 28.5659 27.4951 29.237 26.1779L27.455 25.27ZM19.4 2C21.0967 2 22.309 2.00078 23.2594 2.07842C24.198 2.15512 24.7927 2.30179 25.27 2.54497L26.1779 0.762954C25.3717 0.352173 24.4846 0.171864 23.4222 0.0850662C22.3715 -0.000777706 21.0637 0 19.4 0V2ZM30 10.6C30 8.93634 30.0008 7.62846 29.9149 6.57778C29.8281 5.51543 29.6478 4.62827 29.237 3.82207L27.455 4.73005C27.6982 5.20731 27.8449 5.80197 27.9216 6.74064C27.9992 7.69099 28 8.90334 28 10.6H30ZM25.27 2.54497C26.2108 3.02433 26.9757 3.78924 27.455 4.73005L29.237 3.82207C28.5659 2.50493 27.4951 1.43407 26.1779 0.762954L25.27 2.54497ZM10.6 0C8.93634 0 7.62846 -0.000777706 6.57778 0.0850662C5.51543 0.171864 4.62827 0.352173 3.82207 0.762954L4.73005 2.54497C5.20731 2.30179 5.80197 2.15512 6.74064 2.07842C7.69099 2.00078 8.90334 2 10.6 2V0ZM2 10.6C2 8.90334 2.00078 7.69099 2.07842 6.74064C2.15512 5.80197 2.30179 5.20731 2.54497 4.73005L0.762954 3.82207C0.352173 4.62827 0.171864 5.51543 0.0850662 6.57778C-0.000777706 7.62846 0 8.93634 0 10.6H2ZM3.82207 0.762954C2.50493 1.43407 1.43407 2.50493 0.762954 3.82207L2.54497 4.73005C3.02433 3.78924 3.78924 3.02433 4.73005 2.54497L3.82207 0.762954Z" fill="#414146" mask="url(#path-1-outside-1_6367_338401)"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8 9C8 8.44772 8.44772 8 9 8H12C12.5523 8 13 8.44772 13 9V12C13 12.5523 12.5523 13 12 13H9C8.44772 13 8 12.5523 8 12V9ZM10 11V10H11V11H10Z" fill="#818186"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8 18C8 17.4477 8.44772 17 9 17H12C12.5523 17 13 17.4477 13 18V21C13 21.5523 12.5523 22 12 22H9C8.44772 22 8 21.5523 8 21V18ZM10 20V19H11V20H10Z" fill="#818186"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M18 8C17.4477 8 17 8.44772 17 9V12C17 12.5523 17.4477 13 18 13H21C21.5523 13 22 12.5523 22 12V9C22 8.44772 21.5523 8 21 8H18ZM19 10V11H20V10H19Z" fill="#818186"/>
|
||||
<path d="M16 9C16 8.44772 15.5523 8 15 8C14.4477 8 14 8.44772 14 9V10C14 10.5523 14.4477 11 15 11C15.5523 11 16 10.5523 16 10V9Z" fill="#818186"/>
|
||||
<path d="M15 12C15.5523 12 16 12.4477 16 13V14H18C18.5523 14 19 14.4477 19 15C19 15.5523 18.5523 16 18 16H15C14.4477 16 14 15.5523 14 15V13C14 12.4477 14.4477 12 15 12Z" fill="#818186"/>
|
||||
<path d="M21 14C20.4477 14 20 14.4477 20 15C20 15.5523 20.4477 16 21 16C21.5523 16 22 15.5523 22 15C22 14.4477 21.5523 14 21 14Z" fill="#818186"/>
|
||||
<path d="M14 18C14 17.4477 14.4477 17 15 17H16C16.5523 17 17 17.4477 17 18C17 18.5523 16.5523 19 16 19V21C16 21.5523 15.5523 22 15 22C14.4477 22 14 21.5523 14 21V18Z" fill="#818186"/>
|
||||
<path d="M12 16C12.5523 16 13 15.5523 13 15C13 14.4477 12.5523 14 12 14H9C8.44772 14 8 14.4477 8 15C8 15.5523 8.44772 16 9 16H12Z" fill="#818186"/>
|
||||
<path d="M22 18C22 18.5523 21.5523 19 21 19H19C18.4477 19 18 18.5523 18 18C18 17.4477 18.4477 17 19 17H21C21.5523 17 22 17.4477 22 18Z" fill="#818186"/>
|
||||
<path d="M21 22C21.5523 22 22 21.5523 22 21C22 20.4477 21.5523 20 21 20H18C17.4477 20 17 20.4477 17 21C17 21.5523 17.4477 22 18 22H21Z" fill="#818186"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.5 KiB |
18
public/images/vcs/qr-light.svg
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<mask id="path-1-outside-1_6367_338394" maskUnits="userSpaceOnUse" x="0" y="0" width="30" height="30" fill="black">
|
||||
<rect fill="white" width="30" height="30"/>
|
||||
<path d="M1 10.6C1 7.23969 1 5.55953 1.65396 4.27606C2.2292 3.14708 3.14708 2.2292 4.27606 1.65396C5.55953 1 7.23969 1 10.6 1H19.4C22.7603 1 24.4405 1 25.7239 1.65396C26.8529 2.2292 27.7708 3.14708 28.346 4.27606C29 5.55953 29 7.23969 29 10.6V19.4C29 22.7603 29 24.4405 28.346 25.7239C27.7708 26.8529 26.8529 27.7708 25.7239 28.346C24.4405 29 22.7603 29 19.4 29H10.6C7.23969 29 5.55953 29 4.27606 28.346C3.14708 27.7708 2.2292 26.8529 1.65396 25.7239C1 24.4405 1 22.7603 1 19.4V10.6Z"/>
|
||||
</mask>
|
||||
<path d="M1 10.6C1 7.23969 1 5.55953 1.65396 4.27606C2.2292 3.14708 3.14708 2.2292 4.27606 1.65396C5.55953 1 7.23969 1 10.6 1H19.4C22.7603 1 24.4405 1 25.7239 1.65396C26.8529 2.2292 27.7708 3.14708 28.346 4.27606C29 5.55953 29 7.23969 29 10.6V19.4C29 22.7603 29 24.4405 28.346 25.7239C27.7708 26.8529 26.8529 27.7708 25.7239 28.346C24.4405 29 22.7603 29 19.4 29H10.6C7.23969 29 5.55953 29 4.27606 28.346C3.14708 27.7708 2.2292 26.8529 1.65396 25.7239C1 24.4405 1 22.7603 1 19.4V10.6Z" fill="white"/>
|
||||
<path d="M4.27606 28.346L4.73005 27.455L4.27606 28.346ZM1.65396 25.7239L0.762954 26.1779L1.65396 25.7239ZM28.346 25.7239L29.237 26.1779L28.346 25.7239ZM25.7239 28.346L26.1779 29.237L25.7239 28.346ZM28.346 4.27606L27.455 4.73005L28.346 4.27606ZM10.6 2H19.4V0H10.6V2ZM28 10.6V19.4H30V10.6H28ZM19.4 28H10.6V30H19.4V28ZM2 19.4V10.6H0V19.4H2ZM10.6 28C8.90334 28 7.69099 27.9992 6.74064 27.9216C5.80197 27.8449 5.20731 27.6982 4.73005 27.455L3.82207 29.237C4.62827 29.6478 5.51543 29.8281 6.57778 29.9149C7.62846 30.0008 8.93634 30 10.6 30V28ZM0 19.4C0 21.0637 -0.000777706 22.3715 0.0850662 23.4222C0.171864 24.4846 0.352173 25.3717 0.762954 26.1779L2.54497 25.27C2.30179 24.7927 2.15512 24.198 2.07842 23.2594C2.00078 22.309 2 21.0967 2 19.4H0ZM4.73005 27.455C3.78924 26.9757 3.02433 26.2108 2.54497 25.27L0.762954 26.1779C1.43407 27.4951 2.50493 28.5659 3.82207 29.237L4.73005 27.455ZM28 19.4C28 21.0967 27.9992 22.309 27.9216 23.2594C27.8449 24.198 27.6982 24.7927 27.455 25.27L29.237 26.1779C29.6478 25.3717 29.8281 24.4846 29.9149 23.4222C30.0008 22.3715 30 21.0637 30 19.4H28ZM19.4 30C21.0637 30 22.3715 30.0008 23.4222 29.9149C24.4846 29.8281 25.3717 29.6478 26.1779 29.237L25.27 27.455C24.7927 27.6982 24.198 27.8449 23.2594 27.9216C22.309 27.9992 21.0967 28 19.4 28V30ZM27.455 25.27C26.9757 26.2108 26.2108 26.9757 25.27 27.455L26.1779 29.237C27.4951 28.5659 28.5659 27.4951 29.237 26.1779L27.455 25.27ZM19.4 2C21.0967 2 22.309 2.00078 23.2594 2.07842C24.198 2.15512 24.7927 2.30179 25.27 2.54497L26.1779 0.762954C25.3717 0.352173 24.4846 0.171864 23.4222 0.0850662C22.3715 -0.000777706 21.0637 0 19.4 0V2ZM30 10.6C30 8.93634 30.0008 7.62846 29.9149 6.57778C29.8281 5.51543 29.6478 4.62827 29.237 3.82207L27.455 4.73005C27.6982 5.20731 27.8449 5.80197 27.9216 6.74064C27.9992 7.69099 28 8.90334 28 10.6H30ZM25.27 2.54497C26.2108 3.02433 26.9757 3.78924 27.455 4.73005L29.237 3.82207C28.5659 2.50493 27.4951 1.43407 26.1779 0.762954L25.27 2.54497ZM10.6 0C8.93634 0 7.62846 -0.000777706 6.57778 0.0850662C5.51543 0.171864 4.62827 0.352173 3.82207 0.762954L4.73005 2.54497C5.20731 2.30179 5.80197 2.15512 6.74064 2.07842C7.69099 2.00078 8.90334 2 10.6 2V0ZM2 10.6C2 8.90334 2.00078 7.69099 2.07842 6.74064C2.15512 5.80197 2.30179 5.20731 2.54497 4.73005L0.762954 3.82207C0.352173 4.62827 0.171864 5.51543 0.0850662 6.57778C-0.000777706 7.62846 0 8.93634 0 10.6H2ZM3.82207 0.762954C2.50493 1.43407 1.43407 2.50493 0.762954 3.82207L2.54497 4.73005C3.02433 3.78924 3.78924 3.02433 4.73005 2.54497L3.82207 0.762954Z" fill="#D8D8DB" mask="url(#path-1-outside-1_6367_338394)"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8 9C8 8.44772 8.44772 8 9 8H12C12.5523 8 13 8.44772 13 9V12C13 12.5523 12.5523 13 12 13H9C8.44772 13 8 12.5523 8 12V9ZM10 11V10H11V11H10Z" fill="#97979B"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8 18C8 17.4477 8.44772 17 9 17H12C12.5523 17 13 17.4477 13 18V21C13 21.5523 12.5523 22 12 22H9C8.44772 22 8 21.5523 8 21V18ZM10 20V19H11V20H10Z" fill="#97979B"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M18 8C17.4477 8 17 8.44772 17 9V12C17 12.5523 17.4477 13 18 13H21C21.5523 13 22 12.5523 22 12V9C22 8.44772 21.5523 8 21 8H18ZM19 10V11H20V10H19Z" fill="#97979B"/>
|
||||
<path d="M16 9C16 8.44772 15.5523 8 15 8C14.4477 8 14 8.44772 14 9V10C14 10.5523 14.4477 11 15 11C15.5523 11 16 10.5523 16 10V9Z" fill="#97979B"/>
|
||||
<path d="M15 12C15.5523 12 16 12.4477 16 13V14H18C18.5523 14 19 14.4477 19 15C19 15.5523 18.5523 16 18 16H15C14.4477 16 14 15.5523 14 15V13C14 12.4477 14.4477 12 15 12Z" fill="#97979B"/>
|
||||
<path d="M21 14C20.4477 14 20 14.4477 20 15C20 15.5523 20.4477 16 21 16C21.5523 16 22 15.5523 22 15C22 14.4477 21.5523 14 21 14Z" fill="#97979B"/>
|
||||
<path d="M14 18C14 17.4477 14.4477 17 15 17H16C16.5523 17 17 17.4477 17 18C17 18.5523 16.5523 19 16 19V21C16 21.5523 15.5523 22 15 22C14.4477 22 14 21.5523 14 21V18Z" fill="#97979B"/>
|
||||
<path d="M12 16C12.5523 16 13 15.5523 13 15C13 14.4477 12.5523 14 12 14H9C8.44772 14 8 14.4477 8 15C8 15.5523 8.44772 16 9 16H12Z" fill="#97979B"/>
|
||||
<path d="M22 18C22 18.5523 21.5523 19 21 19H19C18.4477 19 18 18.5523 18 18C18 17.4477 18.4477 17 19 17H21C21.5523 17 22 17.4477 22 18Z" fill="#97979B"/>
|
||||
<path d="M21 22C21.5523 22 22 21.5523 22 21C22 20.4477 21.5523 20 21 20H18C17.4477 20 17 20.4477 17 21C17 21.5523 17.4477 22 18 22H21Z" fill="#97979B"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.4 KiB |
BIN
public/images/vcs/status-building-dark.gif
Normal file
|
After Width: | Height: | Size: 87 KiB |
BIN
public/images/vcs/status-building-light.gif
Normal file
|
After Width: | Height: | Size: 86 KiB |
BIN
public/images/vcs/status-failed-dark.png
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
BIN
public/images/vcs/status-failed-light.png
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
public/images/vcs/status-ready-dark.png
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
public/images/vcs/status-ready-light.png
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
public/images/vcs/status-waiting-dark.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
public/images/vcs/status-waiting-light.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
|
|
@ -93,6 +93,7 @@ abstract class Migration
|
|||
'1.6.0' => 'V21',
|
||||
'1.6.1' => 'V21',
|
||||
'1.6.2' => 'V22',
|
||||
'1.7.0' => 'V23',
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
|
|||
69
src/Appwrite/Migration/Version/V23.php
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Migration\Version;
|
||||
|
||||
use Appwrite\Migration\Migration;
|
||||
use Exception;
|
||||
use Throwable;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Database\Database;
|
||||
|
||||
class V23 extends Migration
|
||||
{
|
||||
/**
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function execute(): void
|
||||
{
|
||||
/**
|
||||
* Disable SubQueries for Performance.
|
||||
*/
|
||||
foreach (['subQueryIndexes', 'subQueryPlatforms', 'subQueryDomains', 'subQueryKeys', 'subQueryWebhooks', 'subQuerySessions', 'subQueryTokens', 'subQueryMemberships', 'subQueryVariables', 'subQueryChallenges', 'subQueryProjectVariables', 'subQueryTargets', 'subQueryTopicTargets'] as $name) {
|
||||
Database::addFilter(
|
||||
$name,
|
||||
fn () => null,
|
||||
fn () => []
|
||||
);
|
||||
}
|
||||
|
||||
Console::info('Migrating Collections');
|
||||
$this->migrateCollections();
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate Collections.
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception|Throwable
|
||||
*/
|
||||
private function migrateCollections(): void
|
||||
{
|
||||
$internalProjectId = $this->project->getInternalId();
|
||||
$collectionType = match ($internalProjectId) {
|
||||
'console' => 'console',
|
||||
default => 'projects',
|
||||
};
|
||||
|
||||
$collections = $this->collections[$collectionType];
|
||||
foreach ($collections as $collection) {
|
||||
$id = $collection['$id'];
|
||||
|
||||
Console::log("Migrating Collection \"{$id}\"");
|
||||
|
||||
$this->projectDB->setNamespace("_$internalProjectId");
|
||||
|
||||
switch ($id) {
|
||||
case 'memberships':
|
||||
// Create roles index
|
||||
try {
|
||||
$this->createIndexFromCollection($this->projectDB, $id, '_key_roles');
|
||||
} catch (Throwable $th) {
|
||||
Console::warning("'_key_roles' from {$id}: {$th->getMessage()}");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
usleep(50000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -27,9 +27,9 @@ class XList extends Base
|
|||
$this
|
||||
->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET)
|
||||
->setHttpPath('/v1/functions/runtimes')
|
||||
->groups(['api', 'functions'])
|
||||
->groups(['api'])
|
||||
->desc('List runtimes')
|
||||
->label('scope', 'functions.read')
|
||||
->label('scope', 'public')
|
||||
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'functions',
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ class XList extends Base
|
|||
->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET)
|
||||
->setHttpPath('/v1/functions/templates')
|
||||
->desc('List templates')
|
||||
->groups(['api', 'functions'])
|
||||
->groups(['api'])
|
||||
->label('scope', 'public')
|
||||
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
|
||||
->label('sdk', new Method(
|
||||
|
|
|
|||
|
|
@ -648,7 +648,11 @@ class Builds extends Action
|
|||
Co::join([
|
||||
Co\go(function () use ($executor, &$response, $project, $deployment, $source, $resource, $runtime, $vars, $command, $cpus, $memory, $timeout, &$err, $version) {
|
||||
try {
|
||||
$command = $version === 'v2' ? 'tar -zxf /tmp/code.tar.gz -C /usr/code && cd /usr/local/src/ && ./build.sh' : 'tar -zxf /tmp/code.tar.gz -C /mnt/code && helpers/build.sh "' . \trim(\escapeshellarg($command), "\'") . '"';
|
||||
if ($version === 'v2') {
|
||||
$command = 'tar -zxf /tmp/code.tar.gz -C /usr/code && cd /usr/local/src/ && ./build.sh';
|
||||
} else {
|
||||
$command = 'tar -zxf /tmp/code.tar.gz -C /mnt/code && helpers/build.sh ' . \trim(\escapeshellarg($command));
|
||||
}
|
||||
|
||||
$response = $executor->createRuntime(
|
||||
deploymentId: $deployment->getId(),
|
||||
|
|
@ -793,7 +797,12 @@ class Builds extends Action
|
|||
foreach ($response['output'] as $log) {
|
||||
$logs .= $log['content'];
|
||||
}
|
||||
$logs .= "[37mCapturing screenshots ...[0m\n";
|
||||
|
||||
if ($resource->getCollection() === 'sites') {
|
||||
$date = \date('H:i:s');
|
||||
$logs .= "[90m[$date] [90m[[0mappwrite[90m][37m Screenshot capturing started. [0m\n";
|
||||
}
|
||||
|
||||
$deployment->setAttribute('buildLogs', $logs);
|
||||
|
||||
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment);
|
||||
|
|
@ -844,8 +853,8 @@ class Builds extends Action
|
|||
]);
|
||||
|
||||
$screenshotError = null;
|
||||
$screenshots = batch(\array_map(function ($key) use ($configs, $deviceForFiles, $apiKey, $resource, $client, $bucket, $project, $dbForPlatform, &$screenshotError) {
|
||||
return function () use ($key, $configs, $deviceForFiles, $apiKey, $resource, $client, $bucket, $project, $dbForPlatform, &$screenshotError) {
|
||||
$screenshots = batch(\array_map(function ($key) use ($configs, $apiKey, $resource, $client, &$screenshotError) {
|
||||
return function () use ($key, $configs, $apiKey, $resource, $client, &$screenshotError) {
|
||||
try {
|
||||
$config = $configs[$key];
|
||||
|
||||
|
|
@ -872,44 +881,7 @@ class Builds extends Action
|
|||
|
||||
$screenshot = $fetchResponse->getBody();
|
||||
|
||||
$fileId = ID::unique();
|
||||
$fileName = $fileId . '.png';
|
||||
$path = $deviceForFiles->getPath($fileName);
|
||||
$path = str_ireplace($deviceForFiles->getRoot(), $deviceForFiles->getRoot() . DIRECTORY_SEPARATOR . $bucket->getId(), $path); // Add bucket id to path after root
|
||||
$success = $deviceForFiles->write($path, $screenshot, "image/png");
|
||||
|
||||
if (!$success) {
|
||||
throw new \Exception("Screenshot failed to save");
|
||||
}
|
||||
|
||||
$teamId = $project->getAttribute('teamId', '');
|
||||
$file = new Document([
|
||||
'$id' => $fileId,
|
||||
'$permissions' => [
|
||||
Permission::read(Role::team(ID::custom($teamId))),
|
||||
],
|
||||
'bucketId' => $bucket->getId(),
|
||||
'bucketInternalId' => $bucket->getInternalId(),
|
||||
'name' => $fileName,
|
||||
'path' => $path,
|
||||
'signature' => $deviceForFiles->getFileHash($path),
|
||||
'mimeType' => $deviceForFiles->getFileMimeType($path),
|
||||
'sizeOriginal' => \strlen($screenshot),
|
||||
'sizeActual' => $deviceForFiles->getFileSize($path),
|
||||
'algorithm' => Compression::GZIP,
|
||||
'comment' => '',
|
||||
'chunksTotal' => 1,
|
||||
'chunksUploaded' => 1,
|
||||
'openSSLVersion' => null,
|
||||
'openSSLCipher' => null,
|
||||
'openSSLTag' => null,
|
||||
'openSSLIV' => null,
|
||||
'search' => implode(' ', [$fileId, $fileName]),
|
||||
'metadata' => ['content_type' => $deviceForFiles->getFileMimeType($path)],
|
||||
]);
|
||||
$file = Authorization::skip(fn () => $dbForPlatform->createDocument('bucket_' . $bucket->getInternalId(), $file));
|
||||
|
||||
return [ 'key' => $key, 'fileId' => $fileId ];
|
||||
return ['key' => $key, 'screenshot' => $screenshot];
|
||||
} catch (\Throwable $th) {
|
||||
$screenshotError = $th->getMessage();
|
||||
return;
|
||||
|
|
@ -921,11 +893,56 @@ class Builds extends Action
|
|||
throw new \Exception($screenshotError);
|
||||
}
|
||||
|
||||
foreach ($screenshots as $screenshot) {
|
||||
$deployment->setAttribute($screenshot['key'], $screenshot['fileId']);
|
||||
foreach ($screenshots as $data) {
|
||||
$key = $data['key'];
|
||||
$screenshot = $data['screenshot'];
|
||||
|
||||
$fileId = ID::unique();
|
||||
$fileName = $fileId . '.png';
|
||||
$path = $deviceForFiles->getPath($fileName);
|
||||
$path = str_ireplace($deviceForFiles->getRoot(), $deviceForFiles->getRoot() . DIRECTORY_SEPARATOR . $bucket->getId(), $path); // Add bucket id to path after root
|
||||
$success = $deviceForFiles->write($path, $screenshot, "image/png");
|
||||
|
||||
if (!$success) {
|
||||
throw new \Exception("Screenshot failed to save");
|
||||
}
|
||||
|
||||
$teamId = $project->getAttribute('teamId', '');
|
||||
$file = new Document([
|
||||
'$id' => $fileId,
|
||||
'$permissions' => [
|
||||
Permission::read(Role::team(ID::custom($teamId))),
|
||||
],
|
||||
'bucketId' => $bucket->getId(),
|
||||
'bucketInternalId' => $bucket->getInternalId(),
|
||||
'name' => $fileName,
|
||||
'path' => $path,
|
||||
'signature' => $deviceForFiles->getFileHash($path),
|
||||
'mimeType' => $deviceForFiles->getFileMimeType($path),
|
||||
'sizeOriginal' => \strlen($screenshot),
|
||||
'sizeActual' => $deviceForFiles->getFileSize($path),
|
||||
'algorithm' => Compression::GZIP,
|
||||
'comment' => '',
|
||||
'chunksTotal' => 1,
|
||||
'chunksUploaded' => 1,
|
||||
'openSSLVersion' => null,
|
||||
'openSSLCipher' => null,
|
||||
'openSSLTag' => null,
|
||||
'openSSLIV' => null,
|
||||
'search' => implode(' ', [$fileId, $fileName]),
|
||||
'metadata' => ['content_type' => $deviceForFiles->getFileMimeType($path)],
|
||||
]);
|
||||
|
||||
Authorization::skip(fn () => $dbForPlatform->createDocument('bucket_' . $bucket->getInternalId(), $file));
|
||||
|
||||
$deployment->setAttribute($key, $fileId);
|
||||
}
|
||||
|
||||
$dbForProject->updateDocument('deployments', $deployment->getId(), $deployment);
|
||||
$logs = $deployment->getAttribute('buildLogs', '');
|
||||
$date = \date('H:i:s');
|
||||
$logs .= "[90m[$date] [90m[[0mappwrite[90m][37m Screenshot capturing finished. [0m\n";
|
||||
|
||||
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment);
|
||||
|
||||
$queueForRealtime
|
||||
->setPayload($deployment->getArrayCopy())
|
||||
|
|
@ -934,9 +951,20 @@ class Builds extends Action
|
|||
Console::warning("Screenshot failed to generate:");
|
||||
Console::warning($th->getMessage());
|
||||
Console::warning($th->getTraceAsString());
|
||||
|
||||
$logs = $deployment->getAttribute('buildLogs', '');
|
||||
$date = \date('H:i:s');
|
||||
$logs .= "[90m[$date] [90m[[0mappwrite[90m][33m Screenshot capturing failed. Deployment will continue. [0m\n";
|
||||
|
||||
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment);
|
||||
}
|
||||
}
|
||||
|
||||
$logs = $deployment->getAttribute('buildLogs', '');
|
||||
$date = \date('H:i:s');
|
||||
$logs .= "[90m[$date] [90m[[0mappwrite[90m][32m Deployment finished. [0m\n";
|
||||
$deployment->setAttribute('buildLogs', $logs);
|
||||
|
||||
/** Update the status */
|
||||
$deployment->setAttribute('status', 'ready');
|
||||
$deployment = $dbForProject->updateDocument('deployments', $deploymentId, $deployment);
|
||||
|
|
@ -1372,15 +1400,9 @@ class Builds extends Action
|
|||
default => throw new \Exception('Invalid resource type')
|
||||
};
|
||||
|
||||
$previweQrCode = match($resource->getCollection()) {
|
||||
'functions' => '',
|
||||
'sites' => 'https://cloud.appwrite.io/v1/avatars/qr?text=' . $previewUrl,
|
||||
default => throw new \Exception('Invalid resource type')
|
||||
};
|
||||
|
||||
$comment = new Comment();
|
||||
$comment->parseComment($github->getComment($owner, $repositoryName, $commentId));
|
||||
$comment->addBuild($project, $resource, $resourceType, $status, $deployment->getId(), ['type' => 'logs'], $previewUrl, $previweQrCode);
|
||||
$comment->addBuild($project, $resource, $resourceType, $status, $deployment->getId(), ['type' => 'logs'], $previewUrl);
|
||||
$github->updateComment($owner, $repositoryName, $commentId, $comment->generateComment());
|
||||
} finally {
|
||||
$dbForPlatform->deleteDocument('vcsCommentLocks', $commentId);
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ class Create extends Base
|
|||
$this
|
||||
->setHttpMethod(Action::HTTP_REQUEST_METHOD_POST)
|
||||
->setHttpPath('/v1/sites/:siteId/deployments/template')
|
||||
->desc('Create deployment')
|
||||
->desc('Create template deployment')
|
||||
->groups(['api', 'sites'])
|
||||
->label('scope', 'sites.write')
|
||||
->label('resourceType', RESOURCE_TYPE_SITES)
|
||||
|
|
|
|||
|
|
@ -27,8 +27,8 @@ class XList extends Base
|
|||
->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET)
|
||||
->setHttpPath('/v1/sites/frameworks')
|
||||
->desc('List frameworks')
|
||||
->groups(['api', 'sites'])
|
||||
->label('scope', 'sites.read')
|
||||
->groups(['api'])
|
||||
->label('scope', 'public')
|
||||
->label('resourceType', RESOURCE_TYPE_SITES)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'sites',
|
||||
|
|
|
|||
|
|
@ -36,6 +36,12 @@ class Preview extends Adapter
|
|||
@import url(https://fonts.bunny.net/css?family=fira-code:400|inter:400);
|
||||
|
||||
#appwrite-preview {
|
||||
min-width: auto;
|
||||
min-height: auto;
|
||||
max-width: none;
|
||||
max-height: none;
|
||||
width: auto;
|
||||
height: auto;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
position: fixed;
|
||||
|
|
|
|||
|
|
@ -9,12 +9,12 @@ class Memberships extends Base
|
|||
'teamId',
|
||||
'invited',
|
||||
'joined',
|
||||
'confirm'
|
||||
'confirm',
|
||||
'roles',
|
||||
];
|
||||
|
||||
/**
|
||||
* Expression constructor
|
||||
*
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -58,6 +58,12 @@ class ConsoleVariables extends Model
|
|||
'default' => '',
|
||||
'example' => 'sites.localhost',
|
||||
])
|
||||
->addRule('_APP_DOMAIN_FUNCTIONS', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'A domain to use for function URLs.',
|
||||
'default' => '',
|
||||
'example' => 'functions.localhost',
|
||||
])
|
||||
->addRule(
|
||||
'_APP_OPTIONS_FORCE_HTTPS',
|
||||
[
|
||||
|
|
|
|||
|
|
@ -9,10 +9,11 @@ use Utopia\System\System;
|
|||
|
||||
class Comment
|
||||
{
|
||||
// TODO: Add more tips
|
||||
protected array $tips = [
|
||||
'Appwrite has a Discord community with over 16 000 members. [Come join us!](https://appwrite.io/discord)',
|
||||
'You can use [Avatars API](https://appwrite.io/docs/client/avatars?sdk=web-default#avatarsGetQR) to generate QR code for any text or URLs',
|
||||
'[Cursor pagination](https://appwrite.io/docs/pagination#cursor-pagination) performs better than offset pagination when loading further pages',
|
||||
'Appwrite has a Discord community with over 16 000 members.',
|
||||
'You can use Avatars API to generate QR code for any text or URLs.',
|
||||
'Cursor pagination performs better than offset pagination when loading further pages.',
|
||||
];
|
||||
|
||||
protected string $statePrefix = '[appwrite]: #';
|
||||
|
|
@ -27,7 +28,7 @@ class Comment
|
|||
return \count($this->builds) === 0;
|
||||
}
|
||||
|
||||
public function addBuild(Document $project, Document $resource, string $resourceType, string $buildStatus, string $deploymentId, array $action, string $previewUrl, string $previewQrCode): void
|
||||
public function addBuild(Document $project, Document $resource, string $resourceType, string $buildStatus, string $deploymentId, array $action, string $previewUrl): void
|
||||
{
|
||||
// Unique index
|
||||
$id = $project->getId() . '_' . $resource->getId();
|
||||
|
|
@ -41,7 +42,6 @@ class Comment
|
|||
'buildStatus' => $buildStatus,
|
||||
'deploymentId' => $deploymentId,
|
||||
'action' => $action,
|
||||
'previewQrCode' => $previewQrCode,
|
||||
'previewUrl' => $previewUrl,
|
||||
];
|
||||
}
|
||||
|
|
@ -70,7 +70,6 @@ class Comment
|
|||
'deploymentId' => $build['deploymentId'],
|
||||
'action' => $build['action'],
|
||||
'previewUrl' => $build['previewUrl'],
|
||||
'previewQrCode' => $build['previewQrCode']
|
||||
];
|
||||
} elseif ($build['resourceType'] === 'function') {
|
||||
$projects[$build['projectId']]['function'][$build['resourceId']] = [
|
||||
|
|
@ -82,32 +81,36 @@ class Comment
|
|||
}
|
||||
}
|
||||
|
||||
$i = 0;
|
||||
foreach ($projects as $projectId => $project) {
|
||||
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https';
|
||||
$hostname = System::getEnv('_APP_DOMAIN');
|
||||
|
||||
$text .= "Project name: **{$project['name']}** \nProject ID: `{$projectId}`\n\n";
|
||||
$text .= "## {$project['name']}\n\n";
|
||||
$text .= "Project ID: `{$projectId}`\n\n";
|
||||
|
||||
$isOpen = $i === 0;
|
||||
|
||||
if (\count($project['site']) > 0) {
|
||||
$text .= "<details" . ($isOpen ? ' open' : '') . ">\n";
|
||||
$text .= "<summary>Sites (" . \count($project['site']) . ")</summary>\n\n";
|
||||
$text .= "<br>\n\n";
|
||||
|
||||
$text .= "| Site | ID | Status | Previews | Action |\n";
|
||||
$text .= "| Site | Status | Logs | Preview | QR\n";
|
||||
$text .= "| :- | :- | :- | :- | :- |\n";
|
||||
|
||||
foreach ($project['site'] as $siteId => $site) {
|
||||
$generateImage = function (string $status) use ($protocol, $hostname) {
|
||||
$extention = $status === 'building' ? 'gif' : 'png';
|
||||
$imagesUrl = $protocol . '://' . $hostname . '/console/images/vcs/';
|
||||
$imageUrl = '<picture><source media="(prefers-color-scheme: dark)" srcset="' . $imagesUrl . 'status-' . $status . '-dark.' . $extention . '"><img alt="' . $status . '" height="25" align="center" src="' . $imagesUrl . 'status-' . $status . '-light.' . $extention . '"></picture>';
|
||||
$extension = $site['status'] === 'building' ? 'gif' : 'png';
|
||||
|
||||
return $imageUrl;
|
||||
};
|
||||
$pathLight = '/images/vcs/status-' . $site['status'] . '-light.' . $extension;
|
||||
$pathDark = '/images/vcs/status-' . $site['status'] . '-dark.' . $extension;
|
||||
|
||||
$status = match ($site['status']) {
|
||||
'waiting' => $generateImage('waiting') . ' Waiting to build',
|
||||
'processing' => $generateImage('processing') . ' Processing',
|
||||
'building' => $generateImage('building') . ' Building',
|
||||
'ready' => $generateImage('ready') . ' Ready',
|
||||
'failed' => $generateImage('failed') . ' Failed',
|
||||
'waiting' => $this->generatImage($pathLight, $pathDark, 'Queued', 85) . ' _Queued_',
|
||||
'processing' => $this->generatImage($pathLight, $pathDark, 'Processing', 85) . ' _Processing_',
|
||||
'building' => $this->generatImage($pathLight, $pathDark, 'Building', 85) . ' _Building_',
|
||||
'ready' => $this->generatImage($pathLight, $pathDark, 'Ready', 85) . ' _Ready_',
|
||||
'failed' => $this->generatImage($pathLight, $pathDark, 'Failed', 85) . ' _Failed_',
|
||||
};
|
||||
|
||||
if ($site['action']['type'] === 'logs') {
|
||||
|
|
@ -116,33 +119,44 @@ class Comment
|
|||
$action = '[Authorize](' . $site['action']['url'] . ')';
|
||||
}
|
||||
|
||||
$previews = '[Preview URL](' . $site['previewUrl'] . ') [QR Code](' . $site['previewQrCode'] . ')';
|
||||
$qrImagePathLight = '/images/vcs/qr-light.svg';
|
||||
$qrImagePathDark = '/images/vcs/qr-dark.svg';
|
||||
|
||||
$text .= "| {$site['name']} | `{$siteId}` | {$status} | {$previews} | {$action} |\n";
|
||||
$consoleUrl = $protocol . '://' . $hostname . '/v1/avatars/qr?text=' . \urlencode($site['previewUrl']);
|
||||
$qr = '[' . $this->generatImage($qrImagePathLight, $qrImagePathDark, 'QR Code', 28) . '](' . $consoleUrl . ')';
|
||||
|
||||
$preview = '[Preview URL](' . $site['previewUrl'] . ')';
|
||||
|
||||
$text .= "| **{$site['name']}**<br>`$siteId`";
|
||||
$text .= "| {$status}";
|
||||
$text .= "| {$action}";
|
||||
$text .= "| {$preview}";
|
||||
$text .= "| {$qr}";
|
||||
$text .= "|\n";
|
||||
}
|
||||
|
||||
$text .= "\n\n";
|
||||
$text .= "\n</details>\n\n";
|
||||
}
|
||||
|
||||
if (\count($project['function']) > 0) {
|
||||
|
||||
$text .= "| Function | ID | Status | Action |\n";
|
||||
$text .= "<details" . ($isOpen ? ' open' : '') . ">\n";
|
||||
$text .= "<summary>Functions (" . \count($project['function']) . ")</summary>\n\n";
|
||||
$text .= "<br>\n\n";
|
||||
$text .= "| Function | ID | Status | Logs |\n";
|
||||
$text .= "| :- | :- | :- | :- |\n";
|
||||
|
||||
foreach ($project['function'] as $functionId => $function) {
|
||||
$generateImage = function (string $status) use ($protocol, $hostname) {
|
||||
$extention = $status === 'building' ? 'gif' : 'png';
|
||||
$imagesUrl = $protocol . '://' . $hostname . '/console/images/vcs/';
|
||||
$imageUrl = '<picture><source media="(prefers-color-scheme: dark)" srcset="' . $imagesUrl . 'status-' . $status . '-dark.' . $extention . '"><img alt="' . $status . '" height="25" align="center" src="' . $imagesUrl . 'status-' . $status . '-light.' . $extention . '"></picture>';
|
||||
return $imageUrl;
|
||||
};
|
||||
$extension = $site['status'] === 'building' ? 'gif' : 'png';
|
||||
|
||||
$status = match ($function['status']) {
|
||||
'waiting' => $generateImage('waiting') . ' Waiting to build',
|
||||
'processing' => $generateImage('processing') . ' Processing',
|
||||
'building' => $generateImage('building') . ' Building',
|
||||
'ready' => $generateImage('ready') . ' Ready',
|
||||
'failed' => $generateImage('failed') . ' Failed',
|
||||
$pathLight = '/images/vcs/status-' . $site['status'] . '-light.' . $extension;
|
||||
$pathDark = '/images/vcs/status-' . $site['status'] . '-dark.' . $extension;
|
||||
|
||||
$status = match ($site['status']) {
|
||||
'waiting' => $this->generatImage($pathLight, $pathDark, 'Queued', 85) . ' _Queued_',
|
||||
'processing' => $this->generatImage($pathLight, $pathDark, 'Processing', 85) . ' _Processing_',
|
||||
'building' => $this->generatImage($pathLight, $pathDark, 'Building', 85) . ' _Building_',
|
||||
'ready' => $this->generatImage($pathLight, $pathDark, 'Ready', 85) . ' _Ready_',
|
||||
'failed' => $this->generatImage($pathLight, $pathDark, 'Failed', 85) . ' _Failed_',
|
||||
};
|
||||
|
||||
if ($function['action']['type'] === 'logs') {
|
||||
|
|
@ -151,22 +165,45 @@ class Comment
|
|||
$action = '[Authorize](' . $function['action']['url'] . ')';
|
||||
}
|
||||
|
||||
$text .= "| {$function['name']} | `{$functionId}` | {$status} | {$action} |\n";
|
||||
$text .= "| **{$function['name']}**<br>`$functionId`";
|
||||
$text .= "| {$status}";
|
||||
$text .= "| {$action}";
|
||||
$text .= "|\n";
|
||||
}
|
||||
|
||||
$text .= "\n\n";
|
||||
$text .= "</details>\n\n";
|
||||
}
|
||||
|
||||
$text .= "</details>\n\n";
|
||||
|
||||
$isLast = $i === \count($projects) - 1;
|
||||
|
||||
if (\count($projects) > 1 && $isLast) {
|
||||
$text .= "---\n\n";
|
||||
}
|
||||
|
||||
$i++;
|
||||
}
|
||||
|
||||
$text .= "Only deployments on the production branch are activated automatically. Learn more about Appwrite [Functions](https://appwrite.io/docs/functions) and [Sites](https://appwrite.io/docs/sites).\n\n";
|
||||
|
||||
$tip = $this->tips[array_rand($this->tips)];
|
||||
$text .= "> **💡 Did you know?** \n " . $tip . "\n\n";
|
||||
$text .= "\n<br>\n\n> [!NOTE]\n> $tip\n\n";
|
||||
|
||||
return $text;
|
||||
}
|
||||
|
||||
public function generatImage(string $pathLight, string $pathDark, string $alt, int $width): string
|
||||
{
|
||||
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https';
|
||||
$hostname = System::getEnv('_APP_DOMAIN');
|
||||
|
||||
$imageLight = $protocol . '://' . $hostname . $pathLight;
|
||||
$imageDark = $protocol . '://' . $hostname . $pathDark;
|
||||
|
||||
$imageUrl = '<picture><source media="(prefers-color-scheme: dark)" srcset="' . $imageDark . '"><img alt="' . $alt . '" width="' . $width . '" align="center" src="' . $imageLight . '"></picture>';
|
||||
|
||||
return $imageUrl;
|
||||
}
|
||||
|
||||
public function parseComment(string $comment): self
|
||||
{
|
||||
$state = \explode("\n", $comment)[0] ?? '';
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ class ConsoleConsoleClientTest extends Scope
|
|||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertCount(10, $response['body']);
|
||||
$this->assertCount(11, $response['body']);
|
||||
$this->assertIsString($response['body']['_APP_DOMAIN_TARGET']);
|
||||
$this->assertIsInt($response['body']['_APP_STORAGE_LIMIT']);
|
||||
$this->assertIsInt($response['body']['_APP_COMPUTE_SIZE_LIMIT']);
|
||||
|
|
@ -33,6 +33,7 @@ class ConsoleConsoleClientTest extends Scope
|
|||
$this->assertIsBool($response['body']['_APP_VCS_ENABLED']);
|
||||
$this->assertIsBool($response['body']['_APP_ASSISTANT_ENABLED']);
|
||||
$this->assertIsString($response['body']['_APP_DOMAIN_SITES']);
|
||||
$this->assertIsString($response['body']['_APP_DOMAIN_FUNCTIONS']);
|
||||
$this->assertIsString($response['body']['_APP_OPTIONS_FORCE_HTTPS']);
|
||||
$this->assertIsString($response['body']['_APP_DOMAINS_NAMESERVERS']);
|
||||
// When adding new keys, dont forget to update count a few lines above
|
||||
|
|
|
|||
|
|
@ -321,7 +321,16 @@ class FunctionsCustomClientTest extends Scope
|
|||
$this->assertContains($template['useCases'][0], ['starter', 'ai']);
|
||||
}
|
||||
$this->assertArrayHasKey('runtimes', $templates['body']['templates'][0]);
|
||||
$this->assertContains('bun-1.0', array_column($templates['body']['templates'][0]['runtimes'], 'name'));
|
||||
|
||||
foreach ($templates['body']['templates'] as $template) {
|
||||
$this->assertThat(
|
||||
\array_column($template['runtimes'], 'name'),
|
||||
$this->logicalOr(
|
||||
$this->containsEqual('bun-1.0'),
|
||||
$this->containsEqual('dart-2.16'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// List templates with pagination and filters
|
||||
$templates = $this->client->call(Client::METHOD_GET, '/functions/templates', array_merge([
|
||||
|
|
|
|||
|
|
@ -1514,7 +1514,7 @@ class FunctionsCustomServerTest extends Scope
|
|||
$runtimes = $this->client->call(Client::METHOD_GET, '/functions/runtimes', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
]));
|
||||
|
||||
$this->assertEquals(200, $runtimes['headers']['status-code']);
|
||||
$this->assertGreaterThan(0, $runtimes['body']['total']);
|
||||
|
|
|
|||
|
|
@ -134,7 +134,7 @@ class AvatarsTest extends Scope
|
|||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertEquals(29428, \strlen($qrCode['body']));
|
||||
$this->assertEquals(9169, \strlen($qrCode['body']));
|
||||
|
||||
return $qrCode['body'];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -252,7 +252,7 @@ class FunctionsServerTest extends Scope
|
|||
$runtimes = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $gqlPayload);
|
||||
]), $gqlPayload);
|
||||
|
||||
$this->assertIsArray($runtimes['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $runtimes['body']);
|
||||
|
|
|
|||
|
|
@ -346,7 +346,7 @@ class ProxyCustomServerTest extends Scope
|
|||
$ruleId = $rule['body']['$id'];
|
||||
|
||||
$rule = $this->updateRuleVerification($ruleId);
|
||||
$this->assertEquals(401, $rule['headers']['status-code']);
|
||||
$this->assertEquals(400, $rule['headers']['status-code']);
|
||||
|
||||
$this->cleanupRule($ruleId);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1377,7 +1377,7 @@ class SitesCustomServerTest extends Scope
|
|||
$frameworks = $this->client->call(Client::METHOD_GET, '/sites/frameworks', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
]));
|
||||
|
||||
$this->assertEquals(200, $frameworks['headers']['status-code']);
|
||||
$this->assertGreaterThan(0, $frameworks['body']['total']);
|
||||
|
|
@ -1736,6 +1736,14 @@ class SitesCustomServerTest extends Scope
|
|||
$this->assertStringContainsString("Preview by", $response['body']);
|
||||
$this->assertGreaterThan($contentLength, $response['headers']['content-length']);
|
||||
|
||||
$response = $proxyClient->call(Client::METHOD_GET, '/non-existing-path', followRedirects: false, headers: [
|
||||
'x-appwrite-key' => API_KEY_DYNAMIC . '_' . $apiKey,
|
||||
]);
|
||||
$this->assertEquals(404, $response['headers']['status-code']);
|
||||
$this->assertStringContainsString("Page not found", $response['body']);
|
||||
$this->assertStringNotContainsString("Preview by", $response['body']);
|
||||
$this->assertGreaterThan($contentLength, $response['headers']['content-length']);
|
||||
|
||||
$this->cleanupSite($siteId);
|
||||
}
|
||||
|
||||
|
|
@ -2464,4 +2472,35 @@ class SitesCustomServerTest extends Scope
|
|||
$this->assertStringContainsString('Sub-directory project1', $response2['body']);
|
||||
$this->cleanupSite($siteId);
|
||||
}
|
||||
|
||||
public function testDeploymentCommandEscaping(): void
|
||||
{
|
||||
$siteId = $this->setupSite([
|
||||
'siteId' => ID::unique(),
|
||||
'name' => 'A site',
|
||||
'framework' => 'other',
|
||||
'adapter' => 'static',
|
||||
'buildRuntime' => 'static-1',
|
||||
'outputDirectory' => './',
|
||||
'buildCommand' => "echo 'Hello two'",
|
||||
'installCommand' => 'echo "Hello one"',
|
||||
'fallbackFile' => '',
|
||||
]);
|
||||
|
||||
$this->assertNotEmpty($siteId);
|
||||
|
||||
$deploymentId = $this->setupDeployment($siteId, [
|
||||
'code' => $this->packageSite('static'),
|
||||
'activate' => 'true'
|
||||
]);
|
||||
|
||||
$this->assertNotEmpty($deploymentId);
|
||||
|
||||
$deployment = $this->getDeployment($siteId, $deploymentId);
|
||||
$this->assertEquals(200, $deployment['headers']['status-code']);
|
||||
$this->assertStringContainsString('Hello one', $deployment['body']['buildLogs']);
|
||||
$this->assertStringContainsString('Hello two', $deployment['body']['buildLogs']);
|
||||
|
||||
$this->cleanupSite($siteId);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -801,6 +801,97 @@ trait UsersBase
|
|||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testGetUser
|
||||
*/
|
||||
public function testListUserMemberships(array $data): array
|
||||
{
|
||||
/**
|
||||
* Test for SUCCESS
|
||||
*/
|
||||
|
||||
// create a new team
|
||||
$team = $this->client->call(Client::METHOD_POST, '/teams', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'teamId' => 'unique()',
|
||||
'name' => 'Test Team',
|
||||
]);
|
||||
|
||||
// create a new membership
|
||||
$membership = $this->client->call(Client::METHOD_POST, '/teams/' . $team['body']['$id'] . '/memberships', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'userId' => $data['userId'],
|
||||
'roles' => ['new-role'],
|
||||
]);
|
||||
|
||||
// list the memberships
|
||||
$response = $this->client->call(Client::METHOD_GET, '/users/' . $data['userId'] . '/memberships', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 200);
|
||||
$this->assertEquals($response['body']['memberships'][0]['$id'], $membership['body']['$id']);
|
||||
$this->assertEquals($response['body']['memberships'][0]['roles'], ['new-role']);
|
||||
$this->assertEquals($response['body']['total'], 1);
|
||||
|
||||
// create another membership with a new role
|
||||
$team = $this->client->call(Client::METHOD_POST, '/teams', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'teamId' => 'unique()',
|
||||
'name' => 'Test Team 2',
|
||||
]);
|
||||
|
||||
$membership = $this->client->call(Client::METHOD_POST, '/teams/' . $team['body']['$id'] . '/memberships', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'userId' => $data['userId'],
|
||||
'roles' => ['new-role-2'],
|
||||
]);
|
||||
|
||||
// list out memberships and query by role
|
||||
$response = $this->client->call(Client::METHOD_GET, '/users/' . $data['userId'] . '/memberships', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => [
|
||||
Query::contains('roles', ['new-role-2'])->toString()
|
||||
]
|
||||
]);
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 200);
|
||||
$this->assertEquals($response['body']['memberships'][0]['$id'], $membership['body']['$id']);
|
||||
$this->assertEquals($response['body']['memberships'][0]['roles'], ['new-role-2']);
|
||||
$this->assertEquals($response['body']['total'], 1);
|
||||
|
||||
/**
|
||||
* Test for FAILURE
|
||||
*/
|
||||
|
||||
// query using equal on array field
|
||||
$response = $this->client->call(Client::METHOD_GET, '/users/' . $data['userId'] . '/memberships', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => [
|
||||
Query::equal('roles', ['new-role-2'])->toString()
|
||||
]
|
||||
]);
|
||||
|
||||
$this->assertEquals($response['body']['code'], 400);
|
||||
$this->assertEquals($response['body']['message'], 'Invalid `queries` param: Invalid query: Cannot query equal on attribute "roles" because it is an array.');
|
||||
$this->assertEquals($response['body']['type'], 'general_argument_invalid');
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testGetUser
|
||||
*/
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 8.9 KiB |
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 7.4 KiB |