mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Merge remote-tracking branch 'origin/1.8.x' into feat-stop-on-failure
This commit is contained in:
+42
-35
@@ -7,7 +7,6 @@ concurrency:
|
||||
env:
|
||||
COMPOSE_FILE: docker-compose.yml
|
||||
IMAGE: appwrite-dev
|
||||
CACHE_KEY: appwrite-dev-${{ github.event.pull_request.head.sha }}
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
@@ -248,11 +247,12 @@ jobs:
|
||||
TESTING=true
|
||||
VERSION=dev
|
||||
|
||||
- name: Cache Docker Image
|
||||
uses: actions/cache@v5
|
||||
- name: Upload Docker Image
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
key: ${{ env.CACHE_KEY }}
|
||||
name: ${{ env.IMAGE }}
|
||||
path: /tmp/${{ env.IMAGE }}.tar
|
||||
retention-days: 1
|
||||
|
||||
unit:
|
||||
name: Tests / Unit
|
||||
@@ -265,12 +265,11 @@ jobs:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Load Cache
|
||||
uses: actions/cache@v5
|
||||
- name: Download Docker Image
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
key: ${{ env.CACHE_KEY }}
|
||||
path: /tmp/${{ env.IMAGE }}.tar
|
||||
fail-on-cache-miss: true
|
||||
name: ${{ env.IMAGE }}
|
||||
path: /tmp
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v4
|
||||
@@ -306,12 +305,11 @@ jobs:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Load Cache
|
||||
uses: actions/cache@v5
|
||||
- name: Download Docker Image
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
key: ${{ env.CACHE_KEY }}
|
||||
path: /tmp/${{ env.IMAGE }}.tar
|
||||
fail-on-cache-miss: true
|
||||
name: ${{ env.IMAGE }}
|
||||
path: /tmp
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v4
|
||||
@@ -349,7 +347,7 @@ jobs:
|
||||
|
||||
e2e_service:
|
||||
name: Tests / E2E / ${{ matrix.database }} (${{ matrix.mode }}) / ${{ matrix.service }}
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ${{ matrix.runner || 'ubuntu-latest' }}
|
||||
needs: [build, matrix]
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -384,16 +382,28 @@ jobs:
|
||||
Messaging,
|
||||
Migrations
|
||||
]
|
||||
include:
|
||||
- service: Databases
|
||||
runner: blacksmith-4vcpu-ubuntu-2404
|
||||
- service: Sites
|
||||
runner: blacksmith-4vcpu-ubuntu-2404
|
||||
- service: Functions
|
||||
runner: blacksmith-4vcpu-ubuntu-2404
|
||||
- service: Avatars
|
||||
runner: blacksmith-4vcpu-ubuntu-2404
|
||||
- service: Realtime
|
||||
runner: blacksmith-4vcpu-ubuntu-2404
|
||||
- service: TablesDB
|
||||
runner: blacksmith-4vcpu-ubuntu-2404
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Load Cache
|
||||
uses: actions/cache@v5
|
||||
- name: Download Docker Image
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
key: ${{ env.CACHE_KEY }}
|
||||
path: /tmp/${{ env.IMAGE }}.tar
|
||||
fail-on-cache-miss: true
|
||||
name: ${{ env.IMAGE }}
|
||||
path: /tmp
|
||||
|
||||
- name: Set database environment
|
||||
run: |
|
||||
@@ -475,12 +485,11 @@ jobs:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Load Cache
|
||||
uses: actions/cache@v5
|
||||
- name: Download Docker Image
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
key: ${{ env.CACHE_KEY }}
|
||||
path: /tmp/${{ env.IMAGE }}.tar
|
||||
fail-on-cache-miss: true
|
||||
name: ${{ env.IMAGE }}
|
||||
path: /tmp
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v4
|
||||
@@ -527,12 +536,11 @@ jobs:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Load Cache
|
||||
uses: actions/cache@v5
|
||||
- name: Download Docker Image
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
key: ${{ env.CACHE_KEY }}
|
||||
path: /tmp/${{ env.IMAGE }}.tar
|
||||
fail-on-cache-miss: true
|
||||
name: ${{ env.IMAGE }}
|
||||
path: /tmp
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v4
|
||||
@@ -581,12 +589,11 @@ jobs:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Load Cache
|
||||
uses: actions/cache@v5
|
||||
- name: Download Docker Image
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
key: ${{ env.CACHE_KEY }}
|
||||
path: /tmp/${{ env.IMAGE }}.tar
|
||||
fail-on-cache-miss: true
|
||||
name: ${{ env.IMAGE }}
|
||||
path: /tmp
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v4
|
||||
|
||||
@@ -4,7 +4,6 @@ use Ahc\Jwt\JWT;
|
||||
use Appwrite\Auth\Validator\MockNumber;
|
||||
use Appwrite\Event\Delete;
|
||||
use Appwrite\Event\Mail;
|
||||
use Appwrite\Event\Validator\Event;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Network\Platform;
|
||||
use Appwrite\Network\Validator\Email;
|
||||
@@ -30,7 +29,6 @@ use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Datetime as DatetimeValidator;
|
||||
use Utopia\Database\Validator\Query\Cursor;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Domains\Validator\PublicDomain;
|
||||
use Utopia\Http\Http;
|
||||
use Utopia\Locale\Locale;
|
||||
use Utopia\System\System;
|
||||
@@ -38,11 +36,9 @@ use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Hostname;
|
||||
use Utopia\Validator\Integer;
|
||||
use Utopia\Validator\Multiple;
|
||||
use Utopia\Validator\Nullable;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\URL;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
Http::init()
|
||||
@@ -773,312 +769,6 @@ Http::delete('/v1/projects/:projectId')
|
||||
$response->noContent();
|
||||
});
|
||||
|
||||
// Webhooks
|
||||
|
||||
Http::post('/v1/projects/:projectId/webhooks')
|
||||
->desc('Create webhook')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'projects',
|
||||
group: 'webhooks',
|
||||
name: 'createWebhook',
|
||||
description: '/docs/references/projects/create-webhook.md',
|
||||
auth: [AuthType::ADMIN],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_CREATED,
|
||||
model: Response::MODEL_WEBHOOK,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('projectId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Project unique ID.', false, ['dbForPlatform'])
|
||||
->param('name', null, new Text(128), 'Webhook name. Max length: 128 chars.')
|
||||
->param('enabled', true, new Boolean(true), 'Enable or disable a webhook.', true)
|
||||
->param('events', null, new ArrayList(new Event(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Events list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' events are allowed.')
|
||||
->param('url', '', fn ($request) => new Multiple([new URL(['http', 'https']), new PublicDomain()], Multiple::TYPE_STRING), 'Webhook URL.', false, ['request'])
|
||||
->param('security', false, new Boolean(true), 'Certificate verification, false for disabled or true for enabled.')
|
||||
->param('httpUser', '', new Text(256), 'Webhook HTTP user. Max length: 256 chars.', true)
|
||||
->param('httpPass', '', new Text(256), 'Webhook HTTP password. Max length: 256 chars.', true)
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
->action(function (string $projectId, string $name, bool $enabled, array $events, string $url, bool $security, string $httpUser, string $httpPass, Response $response, Database $dbForPlatform) {
|
||||
|
||||
$project = $dbForPlatform->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$security = (bool) filter_var($security, FILTER_VALIDATE_BOOLEAN);
|
||||
|
||||
$webhook = new Document([
|
||||
'$id' => ID::unique(),
|
||||
'$permissions' => [
|
||||
Permission::read(Role::any()),
|
||||
Permission::update(Role::any()),
|
||||
Permission::delete(Role::any()),
|
||||
],
|
||||
'projectInternalId' => $project->getSequence(),
|
||||
'projectId' => $project->getId(),
|
||||
'name' => $name,
|
||||
'events' => $events,
|
||||
'url' => $url,
|
||||
'security' => $security,
|
||||
'httpUser' => $httpUser,
|
||||
'httpPass' => $httpPass,
|
||||
'signatureKey' => \bin2hex(\random_bytes(64)),
|
||||
'enabled' => $enabled,
|
||||
]);
|
||||
|
||||
$webhook = $dbForPlatform->createDocument('webhooks', $webhook);
|
||||
|
||||
$dbForPlatform->purgeCachedDocument('projects', $project->getId());
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
->dynamic($webhook, Response::MODEL_WEBHOOK);
|
||||
});
|
||||
|
||||
Http::get('/v1/projects/:projectId/webhooks')
|
||||
->desc('List webhooks')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.read')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'projects',
|
||||
group: 'webhooks',
|
||||
name: 'listWebhooks',
|
||||
description: '/docs/references/projects/list-webhooks.md',
|
||||
auth: [AuthType::ADMIN],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_WEBHOOK_LIST,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('projectId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Project unique ID.', false, ['dbForPlatform'])
|
||||
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
->action(function (string $projectId, bool $includeTotal, Response $response, Database $dbForPlatform) {
|
||||
|
||||
$project = $dbForPlatform->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$webhooks = $dbForPlatform->find('webhooks', [
|
||||
Query::equal('projectInternalId', [$project->getSequence()]),
|
||||
Query::limit(5000),
|
||||
]);
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'webhooks' => $webhooks,
|
||||
'total' => $includeTotal ? count($webhooks) : 0,
|
||||
]), Response::MODEL_WEBHOOK_LIST);
|
||||
});
|
||||
|
||||
Http::get('/v1/projects/:projectId/webhooks/:webhookId')
|
||||
->desc('Get webhook')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.read')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'projects',
|
||||
group: 'webhooks',
|
||||
name: 'getWebhook',
|
||||
description: '/docs/references/projects/get-webhook.md',
|
||||
auth: [AuthType::ADMIN],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_WEBHOOK,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('projectId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Project unique ID.', false, ['dbForPlatform'])
|
||||
->param('webhookId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Webhook unique ID.', false, ['dbForPlatform'])
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
->action(function (string $projectId, string $webhookId, Response $response, Database $dbForPlatform) {
|
||||
|
||||
$project = $dbForPlatform->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$webhook = $dbForPlatform->findOne('webhooks', [
|
||||
Query::equal('$id', [$webhookId]),
|
||||
Query::equal('projectInternalId', [$project->getSequence()]),
|
||||
]);
|
||||
|
||||
if ($webhook->isEmpty()) {
|
||||
throw new Exception(Exception::WEBHOOK_NOT_FOUND);
|
||||
}
|
||||
|
||||
$response->dynamic($webhook, Response::MODEL_WEBHOOK);
|
||||
});
|
||||
|
||||
Http::put('/v1/projects/:projectId/webhooks/:webhookId')
|
||||
->desc('Update webhook')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'projects',
|
||||
group: 'webhooks',
|
||||
name: 'updateWebhook',
|
||||
description: '/docs/references/projects/update-webhook.md',
|
||||
auth: [AuthType::ADMIN],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_WEBHOOK,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('projectId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Project unique ID.', false, ['dbForPlatform'])
|
||||
->param('webhookId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Webhook unique ID.', false, ['dbForPlatform'])
|
||||
->param('name', null, new Text(128), 'Webhook name. Max length: 128 chars.')
|
||||
->param('enabled', true, new Boolean(true), 'Enable or disable a webhook.', true)
|
||||
->param('events', null, new ArrayList(new Event(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Events list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' events are allowed.')
|
||||
->param('url', '', fn ($request) => new Multiple([new URL(['http', 'https']), new PublicDomain()], Multiple::TYPE_STRING), 'Webhook URL.', false, ['request'])
|
||||
->param('security', false, new Boolean(true), 'Certificate verification, false for disabled or true for enabled.')
|
||||
->param('httpUser', '', new Text(256), 'Webhook HTTP user. Max length: 256 chars.', true)
|
||||
->param('httpPass', '', new Text(256), 'Webhook HTTP password. Max length: 256 chars.', true)
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
->action(function (string $projectId, string $webhookId, string $name, bool $enabled, array $events, string $url, bool $security, string $httpUser, string $httpPass, Response $response, Database $dbForPlatform) {
|
||||
|
||||
$project = $dbForPlatform->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$security = ($security === '1' || $security === 'true' || $security === 1 || $security === true);
|
||||
|
||||
$webhook = $dbForPlatform->findOne('webhooks', [
|
||||
Query::equal('$id', [$webhookId]),
|
||||
Query::equal('projectInternalId', [$project->getSequence()]),
|
||||
]);
|
||||
|
||||
if ($webhook->isEmpty()) {
|
||||
throw new Exception(Exception::WEBHOOK_NOT_FOUND);
|
||||
}
|
||||
|
||||
$webhook
|
||||
->setAttribute('name', $name)
|
||||
->setAttribute('events', $events)
|
||||
->setAttribute('url', $url)
|
||||
->setAttribute('security', $security)
|
||||
->setAttribute('httpUser', $httpUser)
|
||||
->setAttribute('httpPass', $httpPass)
|
||||
->setAttribute('enabled', $enabled);
|
||||
|
||||
if ($enabled) {
|
||||
$webhook->setAttribute('attempts', 0);
|
||||
}
|
||||
|
||||
$dbForPlatform->updateDocument('webhooks', $webhook->getId(), $webhook);
|
||||
$dbForPlatform->purgeCachedDocument('projects', $project->getId());
|
||||
|
||||
$response->dynamic($webhook, Response::MODEL_WEBHOOK);
|
||||
});
|
||||
|
||||
Http::patch('/v1/projects/:projectId/webhooks/:webhookId/signature')
|
||||
->desc('Update webhook signature key')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'projects',
|
||||
group: 'webhooks',
|
||||
name: 'updateWebhookSignature',
|
||||
description: '/docs/references/projects/update-webhook-signature.md',
|
||||
auth: [AuthType::ADMIN],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_WEBHOOK,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('projectId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Project unique ID.', false, ['dbForPlatform'])
|
||||
->param('webhookId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Webhook unique ID.', false, ['dbForPlatform'])
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
->action(function (string $projectId, string $webhookId, Response $response, Database $dbForPlatform) {
|
||||
|
||||
$project = $dbForPlatform->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$webhook = $dbForPlatform->findOne('webhooks', [
|
||||
Query::equal('$id', [$webhookId]),
|
||||
Query::equal('projectInternalId', [$project->getSequence()]),
|
||||
]);
|
||||
|
||||
if ($webhook->isEmpty()) {
|
||||
throw new Exception(Exception::WEBHOOK_NOT_FOUND);
|
||||
}
|
||||
|
||||
$webhook->setAttribute('signatureKey', \bin2hex(\random_bytes(64)));
|
||||
|
||||
$dbForPlatform->updateDocument('webhooks', $webhook->getId(), $webhook);
|
||||
$dbForPlatform->purgeCachedDocument('projects', $project->getId());
|
||||
|
||||
$response->dynamic($webhook, Response::MODEL_WEBHOOK);
|
||||
});
|
||||
|
||||
Http::delete('/v1/projects/:projectId/webhooks/:webhookId')
|
||||
->desc('Delete webhook')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'projects',
|
||||
group: 'webhooks',
|
||||
name: 'deleteWebhook',
|
||||
description: '/docs/references/projects/delete-webhook.md',
|
||||
auth: [AuthType::ADMIN],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_NOCONTENT,
|
||||
model: Response::MODEL_NONE,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::NONE
|
||||
))
|
||||
->param('projectId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Project unique ID.', false, ['dbForPlatform'])
|
||||
->param('webhookId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Webhook unique ID.', false, ['dbForPlatform'])
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
->action(function (string $projectId, string $webhookId, Response $response, Database $dbForPlatform) {
|
||||
|
||||
$project = $dbForPlatform->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$webhook = $dbForPlatform->findOne('webhooks', [
|
||||
Query::equal('$id', [$webhookId]),
|
||||
Query::equal('projectInternalId', [$project->getSequence()]),
|
||||
]);
|
||||
|
||||
if ($webhook->isEmpty()) {
|
||||
throw new Exception(Exception::WEBHOOK_NOT_FOUND);
|
||||
}
|
||||
|
||||
$dbForPlatform->deleteDocument('webhooks', $webhook->getId());
|
||||
|
||||
$dbForPlatform->purgeCachedDocument('projects', $project->getId());
|
||||
|
||||
$response->noContent();
|
||||
});
|
||||
|
||||
// Keys
|
||||
|
||||
Http::post('/v1/projects/:projectId/keys')
|
||||
|
||||
@@ -888,7 +888,7 @@ Http::init()
|
||||
$dbForProject = $getProjectDB($project);
|
||||
$request->addFilter(new RequestV20($dbForProject, $route->getPathValues($request)));
|
||||
}
|
||||
if (version_compare($requestFormat, '1.9.0', '<')) {
|
||||
if (version_compare($requestFormat, '1.8.2', '<')) {
|
||||
$request->addFilter(new RequestV21());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ const APP_RESOURCE_TOKEN_ACCESS = 24 * 60 * 60; // 24 hours
|
||||
const APP_FILE_ACCESS = 24 * 60 * 60; // 24 hours
|
||||
const APP_CACHE_UPDATE = 24 * 60 * 60; // 24 hours
|
||||
const APP_CACHE_BUSTER = 4321;
|
||||
const APP_VERSION_STABLE = '1.8.1';
|
||||
const APP_VERSION_STABLE = '1.8.2';
|
||||
const APP_DATABASE_ATTRIBUTE_EMAIL = 'email';
|
||||
const APP_DATABASE_ATTRIBUTE_ENUM = 'enum';
|
||||
const APP_DATABASE_ATTRIBUTE_IP = 'ip';
|
||||
|
||||
+1
-1
@@ -185,7 +185,7 @@ Response::setModel(new BaseList('Runtimes List', Response::MODEL_RUNTIME_LIST, '
|
||||
Response::setModel(new BaseList('Deployments List', Response::MODEL_DEPLOYMENT_LIST, 'deployments', Response::MODEL_DEPLOYMENT));
|
||||
Response::setModel(new BaseList('Executions List', Response::MODEL_EXECUTION_LIST, 'executions', Response::MODEL_EXECUTION));
|
||||
Response::setModel(new BaseList('Projects List', Response::MODEL_PROJECT_LIST, 'projects', Response::MODEL_PROJECT, true, false));
|
||||
Response::setModel(new BaseList('Webhooks List', Response::MODEL_WEBHOOK_LIST, 'webhooks', Response::MODEL_WEBHOOK, true, false));
|
||||
Response::setModel(new BaseList('Webhooks List', Response::MODEL_WEBHOOK_LIST, 'webhooks', Response::MODEL_WEBHOOK, true, true));
|
||||
Response::setModel(new BaseList('API Keys List', Response::MODEL_KEY_LIST, 'keys', Response::MODEL_KEY, true, true));
|
||||
Response::setModel(new BaseList('Dev Keys List', Response::MODEL_DEV_KEY_LIST, 'devKeys', Response::MODEL_DEV_KEY, true, false));
|
||||
Response::setModel(new BaseList('Auth Providers List', Response::MODEL_AUTH_PROVIDER_LIST, 'platforms', Response::MODEL_AUTH_PROVIDER, true, false));
|
||||
|
||||
+26
-7
@@ -476,7 +476,7 @@ Http::setResource('user', function (string $mode, Document $project, Document $c
|
||||
return $user;
|
||||
}, ['mode', 'project', 'console', 'request', 'response', 'dbForProject', 'dbForPlatform', 'store', 'proofForToken', 'authorization']);
|
||||
|
||||
Http::setResource('project', function ($dbForPlatform, $request, $console, $authorization) {
|
||||
Http::setResource('project', function ($dbForPlatform, $request, $console, $authorization, Http $utopia) {
|
||||
/** @var Appwrite\Utopia\Request $request */
|
||||
/** @var Utopia\Database\Database $dbForPlatform */
|
||||
/** @var Utopia\Database\Document $console */
|
||||
@@ -486,6 +486,20 @@ Http::setResource('project', function ($dbForPlatform, $request, $console, $auth
|
||||
$projectId = $request->getHeader('x-appwrite-project', '');
|
||||
}
|
||||
|
||||
// Backwards compatibility for new services, originally project resources
|
||||
// These endpoints moved from /v1/projects/:projectId/<resource> to /v1/<resource>
|
||||
// When accessed via the old alias path, extract projectId from the URI
|
||||
$deprecatedProjectPathPrefix = '/v1/projects/';
|
||||
$route = $utopia->match($request);
|
||||
if (!empty($route)) {
|
||||
$isDeprecatedAlias = \str_starts_with($request->getURI(), $deprecatedProjectPathPrefix) &&
|
||||
!\str_starts_with($route->getPath(), $deprecatedProjectPathPrefix);
|
||||
|
||||
if ($isDeprecatedAlias) {
|
||||
$projectId = \explode('/', $request->getURI(), 5)[3] ?? '';
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($projectId) || $projectId === 'console') {
|
||||
return $console;
|
||||
}
|
||||
@@ -493,7 +507,7 @@ Http::setResource('project', function ($dbForPlatform, $request, $console, $auth
|
||||
$project = $authorization->skip(fn () => $dbForPlatform->getDocument('projects', $projectId));
|
||||
|
||||
return $project;
|
||||
}, ['dbForPlatform', 'request', 'console', 'authorization']);
|
||||
}, ['dbForPlatform', 'request', 'console', 'authorization', 'utopia']);
|
||||
|
||||
Http::setResource('session', function (User $user, Store $store, Token $proofForToken) {
|
||||
if ($user->isEmpty()) {
|
||||
@@ -1072,16 +1086,21 @@ function getDevice(string $root, string $connection = ''): Device
|
||||
}
|
||||
}
|
||||
|
||||
Http::setResource('mode', function ($request) {
|
||||
/** @var Appwrite\Utopia\Request $request */
|
||||
|
||||
Http::setResource('mode', function (Request $request, Document $project) {
|
||||
/**
|
||||
* Defines the mode for the request:
|
||||
* - 'default' => Requests for Client and Server Side
|
||||
* - 'admin' => Request from the Console on non-console projects
|
||||
*/
|
||||
return $request->getParam('mode', $request->getHeader('x-appwrite-mode', APP_MODE_DEFAULT));
|
||||
}, ['request']);
|
||||
$mode = $request->getParam('mode', $request->getHeader('x-appwrite-mode', APP_MODE_DEFAULT));
|
||||
|
||||
$projectId = $request->getParam('project', $request->getHeader('x-appwrite-project', ''));
|
||||
if (!empty($projectId) && $project->getId() !== $projectId) {
|
||||
$mode = APP_MODE_ADMIN;
|
||||
}
|
||||
|
||||
return $mode;
|
||||
}, ['request', 'project']);
|
||||
|
||||
Http::setResource('geodb', function ($register) {
|
||||
/** @var Utopia\Registry\Registry $register */
|
||||
|
||||
@@ -9,6 +9,7 @@ $defaultAppDomain = ($defaultAppDomain === 'traefik') ? 'localhost' : $defaultAp
|
||||
$defaultEmailCertificates = $defaultEmailCertificates ?? '';
|
||||
$defaultDatabase = $vars['_APP_DB_ADAPTER']['default'] ?? 'mongodb';
|
||||
$lockedDatabase = $isUpgrade && empty($lockedDatabase) ? $defaultDatabase : $lockedDatabase;
|
||||
$enabledDatabases = $enabledDatabases ?? ['mongodb', 'mariadb', 'postgresql'];
|
||||
$isLocalInstall = $isLocalInstall ?? false;
|
||||
|
||||
|
||||
@@ -64,6 +65,7 @@ $installerVersion = @filemtime(__DIR__ . '/installer/js/installer.js') ?: time()
|
||||
data-default-secret-key=""
|
||||
data-default-assistant-openai-key=""
|
||||
data-default-database="<?php echo htmlspecialchars((string) $defaultDatabase, ENT_QUOTES, 'UTF-8'); ?>"
|
||||
data-enabled-databases="<?php echo htmlspecialchars(json_encode(array_values($enabledDatabases)), ENT_QUOTES, 'UTF-8'); ?>"
|
||||
<?php if ($isLocalInstall) { ?>
|
||||
data-dev-mode="true"
|
||||
<?php } ?>
|
||||
|
||||
@@ -11,9 +11,13 @@
|
||||
--neutral-300: rgba(173, 173, 176, 1);
|
||||
--neutral-400: rgba(151, 151, 155, 1);
|
||||
--neutral-500: rgba(129, 129, 134, 1);
|
||||
--neutral-600: rgba(108, 108, 113, 1);
|
||||
--neutral-700: rgba(86, 86, 92, 1);
|
||||
--neutral-750: rgba(65, 65, 70, 1);
|
||||
--neutral-800: rgba(45, 45, 49, 1);
|
||||
--neutral-850: rgba(29, 29, 33, 1);
|
||||
--neutral-900: rgba(25, 25, 28, 1);
|
||||
--neutral-250: rgba(195, 195, 198, 1);
|
||||
|
||||
/* Warning colors */
|
||||
--web-orange-200: rgba(255, 213, 194, 1);
|
||||
@@ -172,6 +176,44 @@
|
||||
--fgColor-neutral-weak: var(--fgcolor-neutral-weak);
|
||||
--fgColor-accent: var(--fgcolor-accent);
|
||||
--fgColor-on-accent: var(--fgcolor-on-accent);
|
||||
|
||||
color-scheme: light dark;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--bgcolor-neutral-default: var(--neutral-900);
|
||||
--bgcolor-neutral-primary: var(--neutral-850);
|
||||
--bgcolor-neutral-secondary: var(--neutral-800);
|
||||
--bgcolor-neutral-tertiary: var(--neutral-800);
|
||||
--bgcolor-neutral-invert-weaker: var(--neutral-400);
|
||||
--bgcolor-neutral-invert-weak: var(--neutral-300);
|
||||
--bgcolor-success-weak: rgba(16, 185, 129, 0.12);
|
||||
--bgcolor-warning-weaker: rgba(254, 124, 67, 0.08);
|
||||
--bgcolor-warning-weak: rgba(254, 124, 67, 0.12);
|
||||
--bgcolor-error-weaker: rgba(255, 69, 58, 0.08);
|
||||
|
||||
--fgcolor-neutral-primary: var(--neutral-50);
|
||||
--fgcolor-neutral-secondary: var(--neutral-250);
|
||||
--fgcolor-neutral-tertiary: var(--neutral-500);
|
||||
--fgcolor-neutral-weak: var(--neutral-600);
|
||||
--fgcolor-on-accent: var(--neutral-0);
|
||||
--fgcolor-on-invert: var(--neutral-900);
|
||||
--fgcolor-on-success-weak: rgba(52, 211, 153, 1);
|
||||
--fgcolor-warning: var(--web-orange-500);
|
||||
--fgcolor-on-warning-weak: var(--web-orange-500);
|
||||
--fgcolor-error: var(--web-red-500);
|
||||
--fgcolor-on-error: var(--neutral-0);
|
||||
|
||||
--border-neutral: var(--neutral-800);
|
||||
--border-neutral-strong: var(--neutral-750);
|
||||
--border-neutral-stronger: var(--neutral-600);
|
||||
--border-focus: var(--neutral-600);
|
||||
|
||||
--overlay-neutral-hover: rgba(255, 255, 255, 0.04);
|
||||
--overlay-neutral-pressed: rgba(255, 255, 255, 0.08);
|
||||
--overlay-scrim: rgba(0, 0, 0, 0.48);
|
||||
}
|
||||
}
|
||||
|
||||
.installer-toast-stack {
|
||||
@@ -1354,6 +1396,7 @@ body {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
line-height: 0;
|
||||
color: var(--fgcolor-neutral-weak);
|
||||
}
|
||||
|
||||
.step-indicator svg {
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<svg width="131" height="25" viewBox="0 0 131 25" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||||
<path d="M38.2643 19.5087C40.4069 19.5087 41.49 18.3947 41.9609 17.6362H42.1728C42.267 18.4421 42.832 19.2006 43.9386 19.2006H46.0341V16.8304H45.4926C45.1159 16.8304 44.9275 16.617 44.9275 16.2852V6.78055H42.1492V8.29751H41.9373C41.3958 7.53903 40.2656 6.47242 38.1937 6.47242C34.8974 6.47242 32.4487 9.2219 32.4487 12.9906C32.4487 16.7593 34.9445 19.5087 38.2643 19.5087ZM38.7588 16.8067C36.8045 16.8067 35.2741 15.3608 35.2741 13.0143C35.2741 10.7151 36.7574 9.15079 38.7352 9.15079C40.6188 9.15079 42.1963 10.5492 42.1963 13.0143C42.1963 15.1238 40.8543 16.8067 38.7588 16.8067Z" fill="#19191D"/>
|
||||
<path d="M47.6745 24.0166H50.4528V17.6362H50.6647C51.1826 18.3947 52.2893 19.5087 54.4789 19.5087C57.7752 19.5087 60.1768 16.7118 60.1768 12.9906C60.1768 9.2456 57.6104 6.47242 54.2906 6.47242C52.1715 6.47242 51.1356 7.63384 50.6411 8.2738H50.4292V6.78055H47.6745V24.0166ZM53.8903 16.8778C51.9832 16.8778 50.4057 15.4556 50.4057 12.9906C50.4057 10.8811 51.7477 9.10339 53.8432 9.10339C55.7974 9.10339 57.3279 10.644 57.3279 12.9906C57.3279 15.2897 55.8445 16.8778 53.8903 16.8778Z" fill="#19191D"/>
|
||||
<path d="M61.6104 24.0166H64.3887V17.6362H64.6006C65.1186 18.3947 66.2252 19.5087 68.4149 19.5087C71.7112 19.5087 73.8839 16.7118 73.8839 12.9906C73.8839 9.2456 71.5464 6.47242 68.2265 6.47242C66.1075 6.47242 65.0715 7.63384 64.5771 8.2738H64.3652V6.78055H61.6104V24.0166ZM67.8263 16.8778C65.9191 16.8778 64.3416 15.4556 64.3416 12.9906C64.3416 10.8811 65.6837 9.10339 67.7792 9.10339C69.7334 9.10339 71.2638 10.644 71.2638 12.9906C71.2638 15.2897 69.7805 16.8778 67.8263 16.8778Z" fill="#19191D"/>
|
||||
<path d="M77.5565 19.489H81.4885L83.7252 9.74733H83.8665L86.1033 19.489H90.0117L93.1415 7.06896H90.3414L88.1046 16.8343H87.8927L85.6559 7.06896H81.9594L79.6991 16.8343H79.4872L77.2739 7.06896H74.3073L77.5565 19.489Z" fill="#19191D"/>
|
||||
<path d="M94.549 19.489H97.3273V13.3501C97.3273 11.0036 98.4104 9.55771 100.435 9.55771H101.66V6.76083H100.741C99.1638 6.76083 97.963 7.85114 97.4921 8.89405H97.3038V7.06896H94.549V19.489Z" fill="#19191D"/>
|
||||
<path d="M115.447 19.489H117.613V17.0003H115.47C114.623 17.0003 114.27 16.621 114.27 15.744V9.53401H117.754V7.06896H114.27V3.58472H111.633V7.06896H109.325V9.53401H111.468V15.7677C111.468 18.3987 113.045 19.489 115.447 19.489Z" fill="#19191D"/>
|
||||
<path d="M125.067 19.5087C127.633 19.5087 129.893 18.2288 130.694 15.6452L128.151 15.029C127.704 16.4037 126.409 17.1148 125.043 17.1148C123.018 17.1148 121.676 15.7875 121.653 13.7016H131V12.9195C131 9.2219 128.716 6.47242 124.949 6.47242C121.629 6.47242 118.78 9.10339 118.78 13.0143C118.78 16.8067 121.3 19.5087 125.067 19.5087ZM121.676 11.6632C121.841 10.17 123.183 8.91377 124.949 8.91377C126.644 8.91377 128.033 9.98037 128.175 11.6632H121.676Z" fill="#19191D"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M108.09 19.489H105.312V9.53401H103.145V7.06896H108.09V19.489Z" fill="#19191D"/>
|
||||
<path d="M106.494 5.34533C107.507 5.34533 108.26 4.58686 108.26 3.59136C108.26 2.61956 107.507 1.86108 106.494 1.86108C105.482 1.86108 104.729 2.61956 104.729 3.59136C104.729 4.58686 105.482 5.34533 106.494 5.34533Z" fill="#19191D"/>
|
||||
<path d="M38.2643 19.5087C40.4069 19.5087 41.49 18.3947 41.9609 17.6362H42.1728C42.267 18.4421 42.832 19.2006 43.9386 19.2006H46.0341V16.8304H45.4926C45.1159 16.8304 44.9275 16.617 44.9275 16.2852V6.78055H42.1492V8.29751H41.9373C41.3958 7.53903 40.2656 6.47242 38.1937 6.47242C34.8974 6.47242 32.4487 9.2219 32.4487 12.9906C32.4487 16.7593 34.9445 19.5087 38.2643 19.5087ZM38.7588 16.8067C36.8045 16.8067 35.2741 15.3608 35.2741 13.0143C35.2741 10.7151 36.7574 9.15079 38.7352 9.15079C40.6188 9.15079 42.1963 10.5492 42.1963 13.0143C42.1963 15.1238 40.8543 16.8067 38.7588 16.8067Z" fill="currentColor"/>
|
||||
<path d="M47.6745 24.0166H50.4528V17.6362H50.6647C51.1826 18.3947 52.2893 19.5087 54.4789 19.5087C57.7752 19.5087 60.1768 16.7118 60.1768 12.9906C60.1768 9.2456 57.6104 6.47242 54.2906 6.47242C52.1715 6.47242 51.1356 7.63384 50.6411 8.2738H50.4292V6.78055H47.6745V24.0166ZM53.8903 16.8778C51.9832 16.8778 50.4057 15.4556 50.4057 12.9906C50.4057 10.8811 51.7477 9.10339 53.8432 9.10339C55.7974 9.10339 57.3279 10.644 57.3279 12.9906C57.3279 15.2897 55.8445 16.8778 53.8903 16.8778Z" fill="currentColor"/>
|
||||
<path d="M61.6104 24.0166H64.3887V17.6362H64.6006C65.1186 18.3947 66.2252 19.5087 68.4149 19.5087C71.7112 19.5087 73.8839 16.7118 73.8839 12.9906C73.8839 9.2456 71.5464 6.47242 68.2265 6.47242C66.1075 6.47242 65.0715 7.63384 64.5771 8.2738H64.3652V6.78055H61.6104V24.0166ZM67.8263 16.8778C65.9191 16.8778 64.3416 15.4556 64.3416 12.9906C64.3416 10.8811 65.6837 9.10339 67.7792 9.10339C69.7334 9.10339 71.2638 10.644 71.2638 12.9906C71.2638 15.2897 69.7805 16.8778 67.8263 16.8778Z" fill="currentColor"/>
|
||||
<path d="M77.5565 19.489H81.4885L83.7252 9.74733H83.8665L86.1033 19.489H90.0117L93.1415 7.06896H90.3414L88.1046 16.8343H87.8927L85.6559 7.06896H81.9594L79.6991 16.8343H79.4872L77.2739 7.06896H74.3073L77.5565 19.489Z" fill="currentColor"/>
|
||||
<path d="M94.549 19.489H97.3273V13.3501C97.3273 11.0036 98.4104 9.55771 100.435 9.55771H101.66V6.76083H100.741C99.1638 6.76083 97.963 7.85114 97.4921 8.89405H97.3038V7.06896H94.549V19.489Z" fill="currentColor"/>
|
||||
<path d="M115.447 19.489H117.613V17.0003H115.47C114.623 17.0003 114.27 16.621 114.27 15.744V9.53401H117.754V7.06896H114.27V3.58472H111.633V7.06896H109.325V9.53401H111.468V15.7677C111.468 18.3987 113.045 19.489 115.447 19.489Z" fill="currentColor"/>
|
||||
<path d="M125.067 19.5087C127.633 19.5087 129.893 18.2288 130.694 15.6452L128.151 15.029C127.704 16.4037 126.409 17.1148 125.043 17.1148C123.018 17.1148 121.676 15.7875 121.653 13.7016H131V12.9195C131 9.2219 128.716 6.47242 124.949 6.47242C121.629 6.47242 118.78 9.10339 118.78 13.0143C118.78 16.8067 121.3 19.5087 125.067 19.5087ZM121.676 11.6632C121.841 10.17 123.183 8.91377 124.949 8.91377C126.644 8.91377 128.033 9.98037 128.175 11.6632H121.676Z" fill="currentColor"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M108.09 19.489H105.312V9.53401H103.145V7.06896H108.09V19.489Z" fill="currentColor"/>
|
||||
<path d="M106.494 5.34533C107.507 5.34533 108.26 4.58686 108.26 3.59136C108.26 2.61956 107.507 1.86108 106.494 1.86108C105.482 1.86108 104.729 2.61956 104.729 3.59136C104.729 4.58686 105.482 5.34533 106.494 5.34533Z" fill="currentColor"/>
|
||||
<path d="M24.2577 16.4436V21.9248H10.6705C6.71194 21.9248 3.25559 19.7204 1.40636 16.4436C1.13754 15.9672 0.90225 15.4674 0.704883 14.9487C0.31744 13.9322 0.0738912 12.8415 0 11.7034V10.2214C0.0160422 9.96781 0.0413207 9.71617 0.0743773 9.46752C0.141949 8.95727 0.244035 8.45799 0.378206 7.97265C1.64748 3.37143 5.77469 0 10.6705 0C15.5662 0 19.693 3.37143 20.9622 7.97265H15.1526C14.1988 6.47279 12.5479 5.4812 10.6705 5.4812C8.79305 5.4812 7.14216 6.47279 6.18839 7.97265C5.89768 8.42859 5.67212 8.93136 5.52434 9.46752C5.39308 9.94289 5.32308 10.4442 5.32308 10.9624C5.32308 12.5335 5.96768 13.9497 7.00119 14.9487C7.95886 15.876 9.25 16.4436 10.6705 16.4436H24.2577Z" fill="#FD366E"/>
|
||||
<path d="M24.2578 9.46753V14.9487H14.3398C15.3733 13.9497 16.018 12.5335 16.018 10.9624C16.018 10.4442 15.9479 9.9429 15.8167 9.46753H24.2578Z" fill="#FD366E"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 4.1 KiB |
@@ -1,3 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||||
<path d="M5 8C5 6.34315 6.34315 5 8 5C9.65685 5 11 6.34315 11 8C11 9.65685 9.65685 11 8 11C6.34315 11 5 9.65685 5 8Z" fill="#D8D8DB"/>
|
||||
<path d="M5 8C5 6.34315 6.34315 5 8 5C9.65685 5 11 6.34315 11 8C11 9.65685 9.65685 11 8 11C6.34315 11 5 9.65685 5 8Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 261 B After Width: | Height: | Size: 266 B |
@@ -2,6 +2,11 @@
|
||||
const getBodyDataset = () => document.body?.dataset ?? {};
|
||||
const isUpgradeMode = () => getBodyDataset().upgrade === 'true';
|
||||
const getLockedDatabase = () => getBodyDataset().lockedDatabase || '';
|
||||
const getEnabledDatabases = () => {
|
||||
const raw = getBodyDataset().enabledDatabases;
|
||||
if (!raw) return ['mongodb', 'mariadb', 'postgresql'];
|
||||
try { return JSON.parse(raw); } catch (e) { return ['mongodb', 'mariadb', 'postgresql']; }
|
||||
};
|
||||
|
||||
const STEP_IDS = Object.freeze({
|
||||
CONFIG_FILES: 'config-files',
|
||||
@@ -95,6 +100,7 @@
|
||||
getBodyDataset,
|
||||
isUpgradeMode,
|
||||
getLockedDatabase,
|
||||
getEnabledDatabases,
|
||||
STEP_IDS,
|
||||
STATUS,
|
||||
SSE_EVENTS,
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
|
||||
let activeInstall = null;
|
||||
let unloadGuard = null;
|
||||
let sseSessionDetails = null;
|
||||
const csrfToken = document.querySelector('meta[name="appwrite-installer-csrf"]')?.getAttribute('content') || '';
|
||||
|
||||
const withCsrfHeader = (headers = {}) => {
|
||||
@@ -524,6 +525,9 @@
|
||||
inProgress: payload.message || payload.step,
|
||||
done: payload.message || payload.step
|
||||
};
|
||||
if (step.id === STEP_IDS.ACCOUNT_SETUP && payload.details?.sessionSecret) {
|
||||
sseSessionDetails = payload.details;
|
||||
}
|
||||
progressState.set(step.id, {
|
||||
status: payload.status || STATUS.IN_PROGRESS,
|
||||
message: payload.message,
|
||||
@@ -599,7 +603,7 @@
|
||||
});
|
||||
if (!allDone) return;
|
||||
const accountState = progressState.get(STEP_IDS.ACCOUNT_SETUP);
|
||||
const sessionDetails = accountState?.details;
|
||||
const sessionDetails = sseSessionDetails || accountState?.details;
|
||||
finalizeInstall();
|
||||
notifyInstallComplete(activeInstall?.installId, sessionDetails).finally(() => {
|
||||
setTimeout(() => redirectToApp(), TIMINGS?.redirectDelay ?? 0);
|
||||
@@ -740,7 +744,7 @@
|
||||
}
|
||||
|
||||
const accountState = progressState.get(STEP_IDS.ACCOUNT_SETUP);
|
||||
const sessionDetails = accountState?.details;
|
||||
const sessionDetails = sseSessionDetails || accountState?.details;
|
||||
finalizeInstall();
|
||||
notifyInstallComplete(activeInstall?.installId, sessionDetails).finally(() => {
|
||||
setTimeout(() => redirectToApp(), TIMINGS?.redirectDelay ?? 0);
|
||||
|
||||
@@ -9,7 +9,8 @@
|
||||
const {
|
||||
INSTALLATION_STEPS,
|
||||
clampStep,
|
||||
isUpgradeMode
|
||||
isUpgradeMode,
|
||||
getEnabledDatabases
|
||||
} = Context;
|
||||
|
||||
const {
|
||||
@@ -79,6 +80,19 @@
|
||||
}
|
||||
};
|
||||
|
||||
const applyEnabledDatabases = (root) => {
|
||||
const enabled = getEnabledDatabases?.() || [];
|
||||
const radios = root.querySelectorAll('input[name="database"]');
|
||||
radios.forEach((radio) => {
|
||||
if (!enabled.includes(radio.value)) {
|
||||
const card = radio.closest('.selector-card');
|
||||
if (card) {
|
||||
card.remove();
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const bindDatabaseSelection = (root) => {
|
||||
const radios = root.querySelectorAll('input[name="database"]');
|
||||
radios.forEach((radio) => {
|
||||
@@ -139,6 +153,8 @@
|
||||
return;
|
||||
}
|
||||
|
||||
applyEnabledDatabases(root);
|
||||
|
||||
const lockedDatabase = getLockedDatabase?.() || '';
|
||||
if (lockedDatabase) {
|
||||
lockDatabaseSelection(root, lockedDatabase);
|
||||
|
||||
@@ -7,8 +7,12 @@ $defaultHttpsPort = $defaultHttpsPort ?? '443';
|
||||
$defaultEmailCertificates = $defaultEmailCertificates ?? '';
|
||||
$defaultAssistantOpenAIKey = $defaultAssistantOpenAIKey ?? '';
|
||||
$defaultDatabase = $defaultDatabase ?? 'mongodb';
|
||||
$enabledDatabases = $enabledDatabases ?? ['mongodb', 'mariadb', 'postgresql'];
|
||||
$selectedDatabase = $lockedDatabase ?: $defaultDatabase;
|
||||
$isDatabaseLocked = !empty($lockedDatabase);
|
||||
$mongoEnabled = in_array('mongodb', $enabledDatabases, true);
|
||||
$mariaEnabled = in_array('mariadb', $enabledDatabases, true);
|
||||
$postgresEnabled = in_array('postgresql', $enabledDatabases, true);
|
||||
$mongoDisabled = $isDatabaseLocked && $selectedDatabase !== 'mongodb';
|
||||
$mariaDisabled = $isDatabaseLocked && $selectedDatabase !== 'mariadb';
|
||||
$postgresDisabled = $isDatabaseLocked && $selectedDatabase !== 'postgresql';
|
||||
@@ -43,6 +47,7 @@ $assistantOpenAIKeyValue = htmlspecialchars((string) $defaultAssistantOpenAIKey,
|
||||
<div class="input-group stack-xs">
|
||||
<label class="label-text typography-text-m-500 text-neutral-secondary">Database</label>
|
||||
<div class="selector-group<?php echo $isDatabaseLocked ? ' is-locked' : ''; ?>">
|
||||
<?php if ($mongoEnabled) { ?>
|
||||
<label class="selector-card <?php echo ($selectedDatabase === 'mongodb') ? 'selected' : ''; ?><?php echo $mongoDisabled ? ' is-disabled has-tooltip' : ''; ?>"<?php echo $mongoDisabled ? ' aria-disabled="true"' : ''; ?>>
|
||||
<input type="radio" name="database" value="mongodb" <?php echo ($selectedDatabase === 'mongodb') ? 'checked' : ''; ?> class="sr-only" <?php echo $mongoDisabled ? 'disabled' : ''; ?>>
|
||||
<div class="selector-content">
|
||||
@@ -54,7 +59,9 @@ $assistantOpenAIKeyValue = htmlspecialchars((string) $defaultAssistantOpenAIKey,
|
||||
<span class="tooltip tooltip-db-locked typography-text-m-400 text-on-invert" role="tooltip" data-tooltip-portal="true">Database cannot be changed after initial setup.</span>
|
||||
<?php } ?>
|
||||
</label>
|
||||
<?php } ?>
|
||||
|
||||
<?php if ($mariaEnabled) { ?>
|
||||
<label class="selector-card <?php echo ($selectedDatabase === 'mariadb') ? 'selected' : ''; ?><?php echo $mariaDisabled ? ' is-disabled has-tooltip' : ''; ?>"<?php echo $mariaDisabled ? ' aria-disabled="true"' : ''; ?>>
|
||||
<input type="radio" name="database" value="mariadb" <?php echo ($selectedDatabase === 'mariadb') ? 'checked' : ''; ?> class="sr-only" <?php echo $mariaDisabled ? 'disabled' : ''; ?>>
|
||||
<div class="selector-content">
|
||||
@@ -66,7 +73,9 @@ $assistantOpenAIKeyValue = htmlspecialchars((string) $defaultAssistantOpenAIKey,
|
||||
<span class="tooltip tooltip-db-locked typography-text-m-400 text-on-invert" role="tooltip" data-tooltip-portal="true">Database cannot be changed after initial setup.</span>
|
||||
<?php } ?>
|
||||
</label>
|
||||
<?php } ?>
|
||||
|
||||
<?php if ($postgresEnabled) { ?>
|
||||
<label class="selector-card <?php echo ($selectedDatabase === 'postgresql') ? 'selected' : ''; ?><?php echo $postgresDisabled ? ' is-disabled has-tooltip' : ''; ?>"<?php echo $postgresDisabled ? ' aria-disabled="true"' : ''; ?>>
|
||||
<input type="radio" name="database" value="postgresql" <?php echo ($selectedDatabase === 'postgresql') ? 'checked' : ''; ?> class="sr-only" <?php echo $postgresDisabled ? 'disabled' : ''; ?>>
|
||||
<div class="selector-content">
|
||||
@@ -78,6 +87,7 @@ $assistantOpenAIKeyValue = htmlspecialchars((string) $defaultAssistantOpenAIKey,
|
||||
<span class="tooltip tooltip-db-locked typography-text-m-400 text-on-invert" role="tooltip" data-tooltip-portal="true">Database cannot be changed after initial setup.</span>
|
||||
<?php } ?>
|
||||
</label>
|
||||
<?php } ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
+1
-1
@@ -61,7 +61,7 @@
|
||||
"utopia-php/compression": "0.1.*",
|
||||
"utopia-php/config": "1.*",
|
||||
"utopia-php/console": "0.1.*",
|
||||
"utopia-php/database": "dev-fix-collection-recreate as 5.3.15",
|
||||
"utopia-php/database": "5.*",
|
||||
"utopia-php/detector": "0.2.*",
|
||||
"utopia-php/domains": "1.*",
|
||||
"utopia-php/emails": "0.6.*",
|
||||
|
||||
Generated
+42
-50
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "13579de3d747c541fdcce4f709df8e57",
|
||||
"content-hash": "1404c8821e43b3fe92e06a8ed658ed26",
|
||||
"packages": [
|
||||
{
|
||||
"name": "adhocore/jwt",
|
||||
@@ -686,23 +686,23 @@
|
||||
},
|
||||
{
|
||||
"name": "google/protobuf",
|
||||
"version": "v4.33.5",
|
||||
"version": "v4.33.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/protocolbuffers/protobuf-php.git",
|
||||
"reference": "ebe8010a61b2ae0cff0d246fe1c4d44e9f7dfa6d"
|
||||
"reference": "84b008c23915ed94536737eae46f41ba3bccfe67"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/ebe8010a61b2ae0cff0d246fe1c4d44e9f7dfa6d",
|
||||
"reference": "ebe8010a61b2ae0cff0d246fe1c4d44e9f7dfa6d",
|
||||
"url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/84b008c23915ed94536737eae46f41ba3bccfe67",
|
||||
"reference": "84b008c23915ed94536737eae46f41ba3bccfe67",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": ">=5.0.0 <8.5.27"
|
||||
"phpunit/phpunit": ">=10.5.62 <11.0.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-bcmath": "Need to support JSON deserialization"
|
||||
@@ -724,9 +724,9 @@
|
||||
"proto"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.33.5"
|
||||
"source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.33.6"
|
||||
},
|
||||
"time": "2026-01-29T20:49:00+00:00"
|
||||
"time": "2026-03-18T17:32:05+00:00"
|
||||
},
|
||||
{
|
||||
"name": "halaxa/json-machine",
|
||||
@@ -1996,16 +1996,16 @@
|
||||
},
|
||||
{
|
||||
"name": "phpseclib/phpseclib",
|
||||
"version": "3.0.49",
|
||||
"version": "3.0.50",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpseclib/phpseclib.git",
|
||||
"reference": "6233a1e12584754e6b5daa69fe1289b47775c1b9"
|
||||
"reference": "aa6ad8321ed103dc3624fb600a25b66ebf78ec7b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/6233a1e12584754e6b5daa69fe1289b47775c1b9",
|
||||
"reference": "6233a1e12584754e6b5daa69fe1289b47775c1b9",
|
||||
"url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/aa6ad8321ed103dc3624fb600a25b66ebf78ec7b",
|
||||
"reference": "aa6ad8321ed103dc3624fb600a25b66ebf78ec7b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2086,7 +2086,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/phpseclib/phpseclib/issues",
|
||||
"source": "https://github.com/phpseclib/phpseclib/tree/3.0.49"
|
||||
"source": "https://github.com/phpseclib/phpseclib/tree/3.0.50"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -2102,7 +2102,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-01-27T09:17:28+00:00"
|
||||
"time": "2026-03-19T02:57:58+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/clock",
|
||||
@@ -3850,16 +3850,16 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/database",
|
||||
"version": "dev-fix-collection-recreate",
|
||||
"version": "5.3.16",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/database.git",
|
||||
"reference": "5208630969dfdfbe8eda9c34c6b28ce711ece7c4"
|
||||
"reference": "f121418c4dc7169576735620fd8c17093c3b851d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/database/zipball/5208630969dfdfbe8eda9c34c6b28ce711ece7c4",
|
||||
"reference": "5208630969dfdfbe8eda9c34c6b28ce711ece7c4",
|
||||
"url": "https://api.github.com/repos/utopia-php/database/zipball/f121418c4dc7169576735620fd8c17093c3b851d",
|
||||
"reference": "f121418c4dc7169576735620fd8c17093c3b851d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -3868,9 +3868,10 @@
|
||||
"ext-pdo": "*",
|
||||
"php": ">=8.4",
|
||||
"utopia-php/cache": "1.*",
|
||||
"utopia-php/framework": "0.33.*",
|
||||
"utopia-php/console": "0.1.*",
|
||||
"utopia-php/mongo": "1.*",
|
||||
"utopia-php/pools": "1.*"
|
||||
"utopia-php/pools": "1.*",
|
||||
"utopia-php/validators": "0.2.*"
|
||||
},
|
||||
"require-dev": {
|
||||
"fakerphp/faker": "1.23.*",
|
||||
@@ -3880,7 +3881,7 @@
|
||||
"phpunit/phpunit": "9.*",
|
||||
"rregeer/phpunit-coverage-check": "0.3.*",
|
||||
"swoole/ide-helper": "5.1.3",
|
||||
"utopia-php/cli": "0.14.*"
|
||||
"utopia-php/cli": "0.22.*"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
@@ -3932,10 +3933,10 @@
|
||||
"utopia"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/utopia-php/database/tree/fix-collection-recreate",
|
||||
"source": "https://github.com/utopia-php/database/tree/5.3.16",
|
||||
"issues": "https://github.com/utopia-php/database/issues"
|
||||
},
|
||||
"time": "2026-03-16T11:58:09+00:00"
|
||||
"time": "2026-03-19T10:10:02+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/detector",
|
||||
@@ -4603,16 +4604,16 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/mongo",
|
||||
"version": "1.0.1",
|
||||
"version": "1.0.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/mongo.git",
|
||||
"reference": "83dbcde768d5fb40241f5ca8aa5ed8ca140a7469"
|
||||
"reference": "677a21c53f7a1316c528b4b45b3fce886cee7223"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/mongo/zipball/83dbcde768d5fb40241f5ca8aa5ed8ca140a7469",
|
||||
"reference": "83dbcde768d5fb40241f5ca8aa5ed8ca140a7469",
|
||||
"url": "https://api.github.com/repos/utopia-php/mongo/zipball/677a21c53f7a1316c528b4b45b3fce886cee7223",
|
||||
"reference": "677a21c53f7a1316c528b4b45b3fce886cee7223",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -4658,9 +4659,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/mongo/issues",
|
||||
"source": "https://github.com/utopia-php/mongo/tree/1.0.1"
|
||||
"source": "https://github.com/utopia-php/mongo/tree/1.0.2"
|
||||
},
|
||||
"time": "2026-03-13T07:29:24+00:00"
|
||||
"time": "2026-03-18T02:45:50+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/platform",
|
||||
@@ -5468,16 +5469,16 @@
|
||||
"packages-dev": [
|
||||
{
|
||||
"name": "appwrite/sdk-generator",
|
||||
"version": "1.11.6",
|
||||
"version": "1.11.10",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/appwrite/sdk-generator.git",
|
||||
"reference": "f80e302d000cdc2f98b4bb5ff2fc3bd0bdff7b38"
|
||||
"reference": "96e6b79a241fc615627a820107ca64bbd1b550a6"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/f80e302d000cdc2f98b4bb5ff2fc3bd0bdff7b38",
|
||||
"reference": "f80e302d000cdc2f98b4bb5ff2fc3bd0bdff7b38",
|
||||
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/96e6b79a241fc615627a820107ca64bbd1b550a6",
|
||||
"reference": "96e6b79a241fc615627a820107ca64bbd1b550a6",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -5513,9 +5514,9 @@
|
||||
"description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms",
|
||||
"support": {
|
||||
"issues": "https://github.com/appwrite/sdk-generator/issues",
|
||||
"source": "https://github.com/appwrite/sdk-generator/tree/1.11.6"
|
||||
"source": "https://github.com/appwrite/sdk-generator/tree/1.11.10"
|
||||
},
|
||||
"time": "2026-03-09T07:12:51+00:00"
|
||||
"time": "2026-03-19T05:27:36+00:00"
|
||||
},
|
||||
{
|
||||
"name": "brianium/paratest",
|
||||
@@ -6224,11 +6225,11 @@
|
||||
},
|
||||
{
|
||||
"name": "phpstan/phpstan",
|
||||
"version": "2.1.40",
|
||||
"version": "2.1.42",
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/9b2c7aeb83a75d8680ea5e7c9b7fca88052b766b",
|
||||
"reference": "9b2c7aeb83a75d8680ea5e7c9b7fca88052b766b",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/1279e1ce86ba768f0780c9d889852b4e02ff40d0",
|
||||
"reference": "1279e1ce86ba768f0780c9d889852b4e02ff40d0",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -6273,7 +6274,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2026-02-23T15:04:35+00:00"
|
||||
"time": "2026-03-17T14:58:32+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpunit/php-code-coverage",
|
||||
@@ -8462,18 +8463,9 @@
|
||||
"time": "2024-11-07T12:36:22+00:00"
|
||||
}
|
||||
],
|
||||
"aliases": [
|
||||
{
|
||||
"package": "utopia-php/database",
|
||||
"version": "dev-fix-collection-recreate",
|
||||
"alias": "5.3.15",
|
||||
"alias_normalized": "5.3.15.0"
|
||||
}
|
||||
],
|
||||
"aliases": [],
|
||||
"minimum-stability": "dev",
|
||||
"stability-flags": {
|
||||
"utopia-php/database": 20
|
||||
},
|
||||
"stability-flags": {},
|
||||
"prefer-stable": true,
|
||||
"prefer-lowest": false,
|
||||
"platform": {
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
parameters:
|
||||
ignoreErrors:
|
||||
-
|
||||
message: '#^Variable \$dbForPlatform might not be defined\.$#'
|
||||
identifier: variable.undefined
|
||||
count: 1
|
||||
path: app/cli.php
|
||||
|
||||
-
|
||||
message: '#^PHPDoc tag @var above a method has no effect\.$#'
|
||||
identifier: varTag.misplaced
|
||||
@@ -126,12 +120,6 @@ parameters:
|
||||
count: 1
|
||||
path: app/controllers/shared/api.php
|
||||
|
||||
-
|
||||
message: '#^Variable \$database might not be defined\.$#'
|
||||
identifier: variable.undefined
|
||||
count: 4
|
||||
path: app/http.php
|
||||
|
||||
-
|
||||
message: '#^Variable \$register might not be defined\.$#'
|
||||
identifier: variable.undefined
|
||||
|
||||
@@ -91,6 +91,7 @@ abstract class Migration
|
||||
'1.7.4' => 'V22',
|
||||
'1.8.0' => 'V23',
|
||||
'1.8.1' => 'V23',
|
||||
'1.8.2' => 'V23',
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -48,6 +48,16 @@ class Complete extends Action
|
||||
|
||||
@touch(Server::INSTALLER_COMPLETE_FILE);
|
||||
|
||||
if (!$sessionSecret && $installId !== '') {
|
||||
$data = $state->readProgressFile($installId);
|
||||
$details = $data['details'][Server::STEP_ACCOUNT_SETUP] ?? [];
|
||||
if (!empty($details['sessionSecret'])) {
|
||||
$sessionSecret = $details['sessionSecret'];
|
||||
$sessionId = $sessionId ?: ($details['sessionId'] ?? '');
|
||||
$sessionExpire = $sessionExpire ?: ($details['sessionExpire'] ?? '');
|
||||
}
|
||||
}
|
||||
|
||||
if ($sessionSecret) {
|
||||
$isHttps = $request->getProtocol() === 'https';
|
||||
$sameSite = $isHttps ? Response::COOKIE_SAMESITE_NONE : Response::COOKIE_SAMESITE_LAX;
|
||||
|
||||
@@ -127,6 +127,10 @@ class Install extends Action
|
||||
$this->sendBadRequest($response, $swooleResponse, $wantsStream, 'Please select a supported database');
|
||||
return;
|
||||
}
|
||||
if (!$config->isDatabaseEnabled($database)) {
|
||||
$this->sendBadRequest($response, $swooleResponse, $wantsStream, 'The selected database is not available');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$installId = $state->sanitizeInstallId($installId);
|
||||
|
||||
@@ -44,6 +44,7 @@ class View extends Action
|
||||
$defaultHttpsPort = $config->getDefaultHttpsPort();
|
||||
$isUpgrade = $config->isUpgrade();
|
||||
$lockedDatabase = $config->getLockedDatabase();
|
||||
$enabledDatabases = $config->getEnabledDatabases();
|
||||
$isLocalInstall = $config->isLocal();
|
||||
|
||||
$defaultEmailCertificates = $vars['_APP_EMAIL_CERTIFICATES']['default'] ?? '';
|
||||
|
||||
@@ -14,6 +14,7 @@ final class Config
|
||||
'isLocal',
|
||||
'hostPath',
|
||||
'lockedDatabase',
|
||||
'enabledDatabases',
|
||||
'vars',
|
||||
];
|
||||
|
||||
@@ -26,6 +27,7 @@ final class Config
|
||||
private bool $isLocal = false;
|
||||
private ?string $hostPath = null;
|
||||
private ?string $lockedDatabase = null;
|
||||
private array $enabledDatabases = ['mongodb', 'mariadb'];
|
||||
private array $vars = [];
|
||||
|
||||
public function __construct(array $values = [])
|
||||
@@ -67,6 +69,9 @@ final class Config
|
||||
if ($this->hasValidStringValue($values, 'lockedDatabase')) {
|
||||
$this->setLockedDatabase((string) $values['lockedDatabase']);
|
||||
}
|
||||
if (array_key_exists('enabledDatabases', $values) && is_array($values['enabledDatabases'])) {
|
||||
$this->setEnabledDatabases($values['enabledDatabases']);
|
||||
}
|
||||
if (array_key_exists('vars', $values) && is_array($values['vars'])) {
|
||||
$this->setVars($values['vars']);
|
||||
}
|
||||
@@ -100,6 +105,7 @@ final class Config
|
||||
'isLocal' => $this->isLocal,
|
||||
'hostPath' => $this->hostPath,
|
||||
'lockedDatabase' => $this->lockedDatabase,
|
||||
'enabledDatabases' => $this->enabledDatabases,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -202,4 +208,28 @@ final class Config
|
||||
{
|
||||
$this->lockedDatabase = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getEnabledDatabases(): array
|
||||
{
|
||||
return $this->enabledDatabases;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $value
|
||||
*/
|
||||
public function setEnabledDatabases(array $value): void
|
||||
{
|
||||
$filtered = array_values(array_filter($value, fn ($v) => is_string($v) && $v !== ''));
|
||||
if (!empty($filtered)) {
|
||||
$this->enabledDatabases = $filtered;
|
||||
}
|
||||
}
|
||||
|
||||
public function isDatabaseEnabled(string $adapter): bool
|
||||
{
|
||||
return in_array($adapter, $this->enabledDatabases, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,6 +207,53 @@ class State
|
||||
return sys_get_temp_dir() . '/appwrite-install-' . $installId . '.json';
|
||||
}
|
||||
|
||||
public function clearStaleLock(): void
|
||||
{
|
||||
$this->withGlobalLock(function ($handle, $lock) {
|
||||
if (!$handle) {
|
||||
return;
|
||||
}
|
||||
if ($lock === null) {
|
||||
return;
|
||||
}
|
||||
if ($this->isGlobalLockActive($lock)) {
|
||||
return;
|
||||
}
|
||||
ftruncate($handle, 0);
|
||||
rewind($handle);
|
||||
});
|
||||
|
||||
$tempDir = sys_get_temp_dir();
|
||||
foreach ((array) glob($tempDir . '/appwrite-install-*.json') as $file) {
|
||||
$contents = @file_get_contents($file);
|
||||
if ($contents === false) {
|
||||
continue;
|
||||
}
|
||||
$data = json_decode($contents, true);
|
||||
if (!is_array($data)) {
|
||||
@unlink($file);
|
||||
continue;
|
||||
}
|
||||
$updatedAt = $data['updatedAt'] ?? 0;
|
||||
$age = time() - (int) $updatedAt;
|
||||
$isTerminal = isset($data['error']);
|
||||
if (!$isTerminal && !empty($data['steps'])) {
|
||||
$isTerminal = true;
|
||||
foreach ($data['steps'] as $step) {
|
||||
if (($step['status'] ?? '') !== Server::STATUS_COMPLETED) {
|
||||
$isTerminal = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($age > self::GLOBAL_LOCK_TIMEOUT_SECONDS) {
|
||||
@unlink($file);
|
||||
} elseif ($isTerminal && $age > 60) {
|
||||
@unlink($file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function reserveGlobalLock(string $installId): string
|
||||
{
|
||||
return (string) $this->withGlobalLock(function ($handle, $lock) use ($installId) {
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
namespace Appwrite\Platform\Installer;
|
||||
|
||||
require_once __DIR__ . '/../../../../vendor/autoload.php';
|
||||
|
||||
use Appwrite\Platform\Installer\Http\Installer\Error;
|
||||
use Appwrite\Platform\Installer\Runtime\State;
|
||||
use Swoole\Http\Server as SwooleServer;
|
||||
@@ -129,6 +127,8 @@ class Server
|
||||
|
||||
private function startSwooleServer(string $host, int $port, ?string $readyFile = null): void
|
||||
{
|
||||
$this->state->clearStaleLock();
|
||||
|
||||
// Preload static files into memory
|
||||
$files = new Files();
|
||||
$files->load($this->paths['views']);
|
||||
@@ -311,6 +311,7 @@ function shouldRunInstallerServer(): bool
|
||||
}
|
||||
|
||||
if (shouldRunInstallerServer()) {
|
||||
require_once __DIR__ . '/../../../../vendor/autoload.php';
|
||||
$server = new Server();
|
||||
$server->run();
|
||||
}
|
||||
|
||||
@@ -1048,8 +1048,8 @@ class Builds extends Action
|
||||
'deploymentId' => $deployment->getId(),
|
||||
'deploymentInternalId' => $deployment->getSequence(),
|
||||
'deploymentResourceType' => 'site',
|
||||
'deploymentResourceId' => $deployment->getId(),
|
||||
'deploymentResourceInternalId' => $deployment->getSequence(),
|
||||
'deploymentResourceId' => $resource->getId(),
|
||||
'deploymentResourceInternalId' => $resource->getSequence(),
|
||||
'deploymentVcsProviderBranch' => $branchName,
|
||||
'status' => 'verified',
|
||||
'certificateId' => '',
|
||||
|
||||
@@ -431,7 +431,7 @@ trait Deployment
|
||||
}
|
||||
}
|
||||
|
||||
if ($resource->getCollection() === 'sites' && !empty($latestCommentId) && !empty($previewRuleId)) {
|
||||
if ($resource->getCollection() === 'sites' && !empty($latestCommentId)) {
|
||||
$retries = 0;
|
||||
$lockAcquired = false;
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ class Create extends Base
|
||||
$this
|
||||
->setHttpMethod(Action::HTTP_REQUEST_METHOD_POST)
|
||||
->setHttpPath('/v1/webhooks')
|
||||
->httpAlias('/v1/projects/:projectId/webhooks')
|
||||
->desc('Create webhook')
|
||||
->groups(['api', 'webhooks'])
|
||||
->label('scope', 'webhooks.write')
|
||||
|
||||
@@ -32,6 +32,7 @@ class Delete extends Base
|
||||
$this
|
||||
->setHttpMethod(Action::HTTP_REQUEST_METHOD_DELETE)
|
||||
->setHttpPath('/v1/webhooks/:webhookId')
|
||||
->httpAlias('/v1/projects/:projectId/webhooks/:webhookId')
|
||||
->desc('Delete webhook')
|
||||
->groups(['api', 'webhooks'])
|
||||
->label('scope', 'webhooks.write')
|
||||
|
||||
@@ -30,6 +30,7 @@ class Get extends Base
|
||||
$this
|
||||
->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET)
|
||||
->setHttpPath('/v1/webhooks/:webhookId')
|
||||
->httpAlias('/v1/projects/:projectId/webhooks/:webhookId')
|
||||
->desc('Get webhook')
|
||||
->groups(['api', 'webhooks'])
|
||||
->label('scope', 'webhooks.read')
|
||||
|
||||
@@ -30,6 +30,7 @@ class Update extends Base
|
||||
{
|
||||
$this->setHttpMethod(Action::HTTP_REQUEST_METHOD_PATCH)
|
||||
->setHttpPath('/v1/webhooks/:webhookId/signature')
|
||||
->httpAlias('/v1/projects/:projectId/webhooks/:webhookId/signature')
|
||||
->desc('Update webhook signature key')
|
||||
->groups(['api', 'webhooks'])
|
||||
->label('scope', 'webhooks.write')
|
||||
|
||||
@@ -37,6 +37,7 @@ class Update extends Base
|
||||
{
|
||||
$this->setHttpMethod(Action::HTTP_REQUEST_METHOD_PUT)
|
||||
->setHttpPath('/v1/webhooks/:webhookId')
|
||||
->httpAlias('/v1/projects/:projectId/webhooks/:webhookId')
|
||||
->desc('Update webhook')
|
||||
->groups(['api', 'webhooks'])
|
||||
->label('scope', 'webhooks.write')
|
||||
|
||||
@@ -34,6 +34,7 @@ class XList extends Base
|
||||
$this
|
||||
->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET)
|
||||
->setHttpPath('/v1/webhooks')
|
||||
->httpAlias('/v1/projects/:projectId/webhooks')
|
||||
->desc('List webhooks')
|
||||
->groups(['api', 'webhooks'])
|
||||
->label('scope', 'webhooks.read')
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace Appwrite\Platform\Tasks;
|
||||
|
||||
use Appwrite\Docker\Compose;
|
||||
use Appwrite\Docker\Env;
|
||||
use Appwrite\Platform\Installer\Runtime\State;
|
||||
use Appwrite\Platform\Installer\Server as InstallerServer;
|
||||
use Appwrite\Utopia\View;
|
||||
use Utopia\Auth\Proofs\Password;
|
||||
@@ -23,7 +24,7 @@ class Install extends Action
|
||||
private const int WEB_SERVER_CHECK_DELAY_SECONDS = 1;
|
||||
|
||||
private const int HEALTH_CHECK_ATTEMPTS = 30;
|
||||
private const int HEALTH_CHECK_DELAY_SECONDS = 3;
|
||||
private const int HEALTH_CHECK_DELAY_SECONDS = 1;
|
||||
|
||||
private const string PATTERN_ENV_VAR_NAME = '/^[A-Z0-9_]+$/';
|
||||
private const string PATTERN_DB_PASSWORD_VAR = '/^_APP_DB_.*_PASS$/';
|
||||
@@ -176,6 +177,13 @@ class Install extends Action
|
||||
}
|
||||
}
|
||||
|
||||
$installerConfig = $this->readInstallerConfig();
|
||||
$enabledDatabases = $installerConfig['enabledDatabases'] ?? ['mongodb', 'mariadb'];
|
||||
if (!in_array($database, $enabledDatabases, true)) {
|
||||
Console::error("Database '{$database}' is not available. Available options: " . implode(', ', $enabledDatabases));
|
||||
Console::exit(1);
|
||||
}
|
||||
|
||||
// If interactive and web mode enabled, start web server
|
||||
if ($interactive === 'Y' && Console::isInteractive()) {
|
||||
Console::success('Starting web installer...');
|
||||
@@ -295,6 +303,12 @@ class Install extends Action
|
||||
|
||||
@unlink(InstallerServer::INSTALLER_COMPLETE_FILE);
|
||||
|
||||
$state = new State([]);
|
||||
$state->clearStaleLock();
|
||||
|
||||
$installerConfig = $this->readInstallerConfig();
|
||||
$enabledDatabases = $installerConfig['enabledDatabases'] ?? ['mongodb', 'mariadb'];
|
||||
|
||||
$this->setInstallerConfig([
|
||||
'defaultHttpPort' => $defaultHttpPort,
|
||||
'defaultHttpsPort' => $defaultHttpsPort,
|
||||
@@ -304,6 +318,7 @@ class Install extends Action
|
||||
'vars' => $vars,
|
||||
'isUpgrade' => $isUpgrade,
|
||||
'lockedDatabase' => $lockedDatabase,
|
||||
'enabledDatabases' => $enabledDatabases,
|
||||
'isLocal' => $this->isLocalInstall(),
|
||||
'hostPath' => $this->hostPath ?: null,
|
||||
]);
|
||||
@@ -734,8 +749,8 @@ class Install extends Action
|
||||
{
|
||||
$client = new Client();
|
||||
$client
|
||||
->setTimeout(5000)
|
||||
->setConnectTimeout(5000)
|
||||
->setTimeout(2000)
|
||||
->setConnectTimeout(2000)
|
||||
->addHeader('Host', $domain);
|
||||
|
||||
$healthPath = '/v1/health/version';
|
||||
|
||||
@@ -1008,7 +1008,6 @@ class Deletes extends Action
|
||||
*/
|
||||
Console::info("Deleting rules for site " . $siteId);
|
||||
$this->deleteByGroup('rules', [
|
||||
Query::equal('type', ['deployment']),
|
||||
Query::equal('deploymentResourceType', ['site']),
|
||||
Query::equal('deploymentResourceInternalId', [$siteInternalId]),
|
||||
Query::equal('projectInternalId', [$project->getSequence()])
|
||||
@@ -1094,7 +1093,6 @@ class Deletes extends Action
|
||||
*/
|
||||
Console::info("Deleting rules for function " . $functionId);
|
||||
$this->deleteByGroup('rules', [
|
||||
Query::equal('type', ['deployment']),
|
||||
Query::equal('deploymentResourceType', ['function']),
|
||||
Query::equal('deploymentResourceInternalId', [$functionInternalId]),
|
||||
Query::equal('projectInternalId', [$project->getSequence()]),
|
||||
|
||||
@@ -6,10 +6,13 @@ use Appwrite\Utopia\Request\Filter;
|
||||
|
||||
class V21 extends Filter
|
||||
{
|
||||
// Convert 1.8.0 params to 1.9.0
|
||||
// Convert 1.8.0 params to 1.8.2
|
||||
public function parse(array $content, string $model): array
|
||||
{
|
||||
switch ($model) {
|
||||
case 'webhooks.create':
|
||||
$content = $this->fillWebhookid($content);
|
||||
break;
|
||||
case 'functions.createTemplateDeployment':
|
||||
case 'sites.createTemplateDeployment':
|
||||
$content = $this->convertVersionToTypeAndReference($content);
|
||||
@@ -48,4 +51,10 @@ class V21 extends Filter
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
protected function fillWebhookid(array $content): array
|
||||
{
|
||||
$content['webhookId'] = $content['webhookId'] ?? 'unique()';
|
||||
return $content;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ class Webhook extends Model
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected bool $public = false;
|
||||
protected bool $public = true;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
|
||||
@@ -193,12 +193,14 @@ trait ProjectCustom
|
||||
$this->assertNotEmpty($devKey['body']);
|
||||
$this->assertNotEmpty($devKey['body']['secret']);
|
||||
|
||||
$webhook = $this->client->call(Client::METHOD_POST, '/projects/' . $project['body']['$id'] . '/webhooks', [
|
||||
$webhook = $this->client->call(Client::METHOD_POST, '/webhooks', [
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'cookie' => 'a_session_console=' . $this->getRoot()['session'],
|
||||
'x-appwrite-project' => 'console',
|
||||
'x-appwrite-project' => $project['body']['$id'],
|
||||
'x-appwrite-mode' => 'admin'
|
||||
], [
|
||||
'webhookId' => 'unique()',
|
||||
'name' => 'Webhook Test',
|
||||
'events' => [
|
||||
'databases.*',
|
||||
|
||||
@@ -1727,6 +1727,70 @@ class FunctionsCustomServerTest extends Scope
|
||||
$this->assertEquals(404, $function['headers']['status-code']);
|
||||
}
|
||||
|
||||
public function testDeleteFunctionRulesCleanup(): void
|
||||
{
|
||||
$functionId = $this->setupFunction([
|
||||
'functionId' => ID::unique(),
|
||||
'name' => 'Test Rules Cleanup Function',
|
||||
'runtime' => 'node-22',
|
||||
'entrypoint' => 'index.js',
|
||||
'timeout' => 15,
|
||||
]);
|
||||
|
||||
$this->assertNotEmpty($functionId);
|
||||
|
||||
// Create a manual deployment rule (type = 'deployment')
|
||||
$domain = $this->setupFunctionDomain($functionId);
|
||||
$this->assertNotEmpty($domain);
|
||||
|
||||
// Create a redirect rule (type = 'redirect')
|
||||
$redirectDomain = \uniqid() . '-redirect-cleanup.custom.localhost';
|
||||
$redirectRule = $this->client->call(Client::METHOD_POST, '/proxy/rules/redirect', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'domain' => $redirectDomain,
|
||||
'url' => 'https://appwrite.io',
|
||||
'statusCode' => 301,
|
||||
'resourceType' => 'function',
|
||||
'resourceId' => $functionId,
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $redirectRule['headers']['status-code']);
|
||||
$this->assertNotEmpty($redirectRule['body']['$id']);
|
||||
|
||||
// Verify both rules exist (no type filter — catches all rule types)
|
||||
$rules = $this->client->call(Client::METHOD_GET, '/proxy/rules', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => [
|
||||
Query::equal('deploymentResourceId', [$functionId])->toString()
|
||||
]
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $rules['headers']['status-code']);
|
||||
$this->assertGreaterThanOrEqual(2, $rules['body']['total']);
|
||||
|
||||
// Delete the function
|
||||
$this->cleanupFunction($functionId);
|
||||
|
||||
// Verify ALL rules (deployment + redirect) are cleaned up
|
||||
$this->assertEventually(function () use ($functionId) {
|
||||
$rules = $this->client->call(Client::METHOD_GET, '/proxy/rules', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => [
|
||||
Query::equal('deploymentResourceId', [$functionId])->toString()
|
||||
]
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $rules['headers']['status-code']);
|
||||
$this->assertEquals(0, $rules['body']['total']);
|
||||
}, 5000, 500);
|
||||
}
|
||||
|
||||
public function testExecutionTimeout()
|
||||
{
|
||||
$functionId = $this->setupFunction([
|
||||
|
||||
@@ -1711,6 +1711,7 @@ trait WebhooksBase
|
||||
'content-type' => 'application/json',
|
||||
'cookie' => 'a_session_console=' . $this->getRoot()['session'],
|
||||
'x-appwrite-project' => 'console',
|
||||
'X-Appwrite-Response-Format' => '1.8.0'
|
||||
], [
|
||||
'name' => 'Webhook Test',
|
||||
'enabled' => true,
|
||||
@@ -1740,6 +1741,7 @@ trait WebhooksBase
|
||||
'content-type' => 'application/json',
|
||||
'cookie' => 'a_session_console=' . $this->getRoot()['session'],
|
||||
'x-appwrite-project' => 'console',
|
||||
'X-Appwrite-Response-Format' => '1.8.0'
|
||||
], [
|
||||
'name' => 'Webhook Test',
|
||||
'enabled' => true,
|
||||
@@ -1779,6 +1781,7 @@ trait WebhooksBase
|
||||
'content-type' => 'application/json',
|
||||
'cookie' => 'a_session_console=' . $this->getRoot()['session'],
|
||||
'x-appwrite-project' => 'console',
|
||||
'X-Appwrite-Response-Format' => '1.8.0'
|
||||
], [
|
||||
'name' => 'Webhook Test',
|
||||
'enabled' => true,
|
||||
@@ -1824,6 +1827,7 @@ trait WebhooksBase
|
||||
'content-type' => 'application/json',
|
||||
'cookie' => 'a_session_console=' . $this->getRoot()['session'],
|
||||
'x-appwrite-project' => 'console',
|
||||
'X-Appwrite-Response-Format' => '1.8.0'
|
||||
]));
|
||||
|
||||
// assert that the webhook is now disabled after 10 consecutive failures
|
||||
|
||||
@@ -81,10 +81,12 @@ trait ProjectsBase
|
||||
$projectData = $this->setupProjectData();
|
||||
$id = $projectData['projectId'];
|
||||
|
||||
$response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/webhooks', array_merge([
|
||||
$response = $this->client->call(Client::METHOD_POST, '/webhooks', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-project' => $id,
|
||||
'x-appwrite-mode' => 'admin',
|
||||
], $this->getHeaders()), [
|
||||
'webhookId' => 'unique()',
|
||||
'name' => 'Webhook Test',
|
||||
'events' => ['users.*.create', 'users.*.update.email'],
|
||||
'url' => 'https://appwrite.io',
|
||||
|
||||
@@ -2743,10 +2743,12 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$data = $this->setupProjectData();
|
||||
$id = $data['projectId'];
|
||||
|
||||
$response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/webhooks', array_merge([
|
||||
$response = $this->client->call(Client::METHOD_POST, '/webhooks', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-project' => $id,
|
||||
'x-appwrite-mode' => 'admin'
|
||||
], $this->getHeaders()), [
|
||||
'webhookId' => 'unique()',
|
||||
'name' => 'Webhook Test',
|
||||
'events' => ['users.*.create', 'users.*.update.email'],
|
||||
'url' => 'https://appwrite.io',
|
||||
@@ -2768,10 +2770,12 @@ class ProjectsConsoleClientTest extends Scope
|
||||
/**
|
||||
* Test for FAILURE
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/webhooks', array_merge([
|
||||
$response = $this->client->call(Client::METHOD_POST, '/webhooks', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-project' => $id,
|
||||
'x-appwrite-mode' => 'admin'
|
||||
], $this->getHeaders()), [
|
||||
'webhookId' => 'unique()',
|
||||
'name' => 'Webhook Test',
|
||||
'events' => ['account.unknown', 'users.*.update.email'],
|
||||
'url' => 'https://appwrite.io',
|
||||
@@ -2782,10 +2786,12 @@ class ProjectsConsoleClientTest extends Scope
|
||||
|
||||
$this->assertEquals(400, $response['headers']['status-code']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/webhooks', array_merge([
|
||||
$response = $this->client->call(Client::METHOD_POST, '/webhooks', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-project' => $id,
|
||||
'x-appwrite-mode' => 'admin'
|
||||
], $this->getHeaders()), [
|
||||
'webhookId' => 'unique()',
|
||||
'name' => 'Webhook Test',
|
||||
'events' => ['users.*.create', 'users.*.update.email'],
|
||||
'url' => 'invalid://appwrite.io',
|
||||
@@ -2799,9 +2805,10 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$data = $this->setupProjectWithWebhook();
|
||||
$id = $data['projectId'];
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/webhooks', array_merge([
|
||||
$response = $this->client->call(Client::METHOD_GET, '/webhooks', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-project' => $id,
|
||||
'x-appwrite-mode' => 'admin'
|
||||
], $this->getHeaders()), []);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
@@ -2819,9 +2826,10 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$id = $data['projectId'];
|
||||
$webhookId = $data['webhookId'];
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/webhooks/' . $webhookId, array_merge([
|
||||
$response = $this->client->call(Client::METHOD_GET, '/webhooks/' . $webhookId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-project' => $id,
|
||||
'x-appwrite-mode' => 'admin'
|
||||
], $this->getHeaders()), []);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
@@ -2837,9 +2845,10 @@ class ProjectsConsoleClientTest extends Scope
|
||||
/**
|
||||
* Test for FAILURE
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/webhooks/error', array_merge([
|
||||
$response = $this->client->call(Client::METHOD_GET, '/webhooks/error', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-project' => $id,
|
||||
'x-appwrite-mode' => 'admin'
|
||||
], $this->getHeaders()), []);
|
||||
|
||||
$this->assertEquals(404, $response['headers']['status-code']);
|
||||
@@ -2851,9 +2860,10 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$id = $data['projectId'];
|
||||
$webhookId = $data['webhookId'];
|
||||
|
||||
$response = $this->client->call(Client::METHOD_PUT, '/projects/' . $id . '/webhooks/' . $webhookId, array_merge([
|
||||
$response = $this->client->call(Client::METHOD_PUT, '/webhooks/' . $webhookId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-project' => $id,
|
||||
'x-appwrite-mode' => 'admin'
|
||||
], $this->getHeaders()), [
|
||||
'name' => 'Webhook Test Update',
|
||||
'events' => ['users.*.delete', 'users.*.sessions.*.delete', 'buckets.*.files.*.create'],
|
||||
@@ -2875,9 +2885,10 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$this->assertEquals('', $response['body']['httpUser']);
|
||||
$this->assertEquals('', $response['body']['httpPass']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/webhooks/' . $webhookId, array_merge([
|
||||
$response = $this->client->call(Client::METHOD_GET, '/webhooks/' . $webhookId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-project' => $id,
|
||||
'x-appwrite-mode' => 'admin'
|
||||
], $this->getHeaders()), []);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
@@ -2897,9 +2908,10 @@ class ProjectsConsoleClientTest extends Scope
|
||||
/**
|
||||
* Test for FAILURE
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_PUT, '/projects/' . $id . '/webhooks/' . $webhookId, array_merge([
|
||||
$response = $this->client->call(Client::METHOD_PUT, '/webhooks/' . $webhookId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-project' => $id,
|
||||
'x-appwrite-mode' => 'admin'
|
||||
], $this->getHeaders()), [
|
||||
'name' => 'Webhook Test Update',
|
||||
'events' => ['users.*.delete', 'users.*.sessions.*.delete', 'buckets.*.files.*.unknown'],
|
||||
@@ -2909,9 +2921,10 @@ class ProjectsConsoleClientTest extends Scope
|
||||
|
||||
$this->assertEquals(400, $response['headers']['status-code']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_PUT, '/projects/' . $id . '/webhooks/' . $webhookId, array_merge([
|
||||
$response = $this->client->call(Client::METHOD_PUT, '/webhooks/' . $webhookId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-project' => $id,
|
||||
'x-appwrite-mode' => 'admin'
|
||||
], $this->getHeaders()), [
|
||||
'name' => 'Webhook Test Update',
|
||||
'events' => ['users.*.delete', 'users.*.sessions.*.delete', 'buckets.*.files.*.create'],
|
||||
@@ -2921,9 +2934,10 @@ class ProjectsConsoleClientTest extends Scope
|
||||
|
||||
$this->assertEquals(400, $response['headers']['status-code']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_PUT, '/projects/' . $id . '/webhooks/' . $webhookId, array_merge([
|
||||
$response = $this->client->call(Client::METHOD_PUT, '/webhooks/' . $webhookId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-project' => $id,
|
||||
'x-appwrite-mode' => 'admin'
|
||||
], $this->getHeaders()), [
|
||||
'name' => 'Webhook Test Update',
|
||||
'events' => ['users.*.delete', 'users.*.sessions.*.delete', 'buckets.*.files.*.create'],
|
||||
@@ -2940,9 +2954,10 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$webhookId = $data['webhookId'];
|
||||
$signatureKey = $data['signatureKey'];
|
||||
|
||||
$response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $id . '/webhooks/' . $webhookId . '/signature', array_merge([
|
||||
$response = $this->client->call(Client::METHOD_PATCH, '/webhooks/' . $webhookId . '/signature', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-project' => $id,
|
||||
'x-appwrite-mode' => 'admin'
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
@@ -2957,10 +2972,12 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$id = $projectData['projectId'];
|
||||
|
||||
// Create a webhook to delete
|
||||
$response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/webhooks', array_merge([
|
||||
$response = $this->client->call(Client::METHOD_POST, '/webhooks', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-project' => $id,
|
||||
'x-appwrite-mode' => 'admin'
|
||||
], $this->getHeaders()), [
|
||||
'webhookId' => 'unique()',
|
||||
'name' => 'Webhook To Delete',
|
||||
'events' => ['users.*.create'],
|
||||
'url' => 'https://appwrite.io',
|
||||
@@ -2972,17 +2989,19 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
$webhookId = $response['body']['$id'];
|
||||
|
||||
$response = $this->client->call(Client::METHOD_DELETE, '/projects/' . $id . '/webhooks/' . $webhookId, array_merge([
|
||||
$response = $this->client->call(Client::METHOD_DELETE, '/webhooks/' . $webhookId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-project' => $id,
|
||||
'x-appwrite-mode' => 'admin'
|
||||
], $this->getHeaders()), []);
|
||||
|
||||
$this->assertEquals(204, $response['headers']['status-code']);
|
||||
$this->assertEmpty($response['body']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/webhooks/' . $webhookId, array_merge([
|
||||
$response = $this->client->call(Client::METHOD_GET, '/webhooks/' . $webhookId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-project' => $id,
|
||||
'x-appwrite-mode' => 'admin'
|
||||
], $this->getHeaders()), []);
|
||||
|
||||
$this->assertEquals(404, $response['headers']['status-code']);
|
||||
@@ -2990,9 +3009,10 @@ class ProjectsConsoleClientTest extends Scope
|
||||
/**
|
||||
* Test for FAILURE
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_DELETE, '/projects/' . $id . '/webhooks/error', array_merge([
|
||||
$response = $this->client->call(Client::METHOD_DELETE, '/webhooks/error', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-project' => $id,
|
||||
'x-appwrite-mode' => 'admin'
|
||||
], $this->getHeaders()), []);
|
||||
|
||||
$this->assertEquals(404, $response['headers']['status-code']);
|
||||
@@ -4350,9 +4370,10 @@ class ProjectsConsoleClientTest extends Scope
|
||||
/**
|
||||
* Test for FAILURE
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_DELETE, '/projects/' . $id . '/webhooks/error', array_merge([
|
||||
$response = $this->client->call(Client::METHOD_DELETE, '/webhooks/error', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-project' => $id,
|
||||
'x-appwrite-mode' => 'admin'
|
||||
], $this->getHeaders()), []);
|
||||
|
||||
$this->assertEquals(404, $response['headers']['status-code']);
|
||||
|
||||
@@ -1439,6 +1439,71 @@ class SitesCustomServerTest extends Scope
|
||||
$this->assertEquals(404, $function['headers']['status-code']);
|
||||
}
|
||||
|
||||
public function testDeleteSiteRulesCleanup(): void
|
||||
{
|
||||
$siteId = $this->setupSite([
|
||||
'siteId' => ID::unique(),
|
||||
'name' => 'Test Rules Cleanup Site',
|
||||
'framework' => 'other',
|
||||
'buildRuntime' => 'node-22',
|
||||
'outputDirectory' => './',
|
||||
'fallbackFile' => '',
|
||||
]);
|
||||
|
||||
$this->assertNotEmpty($siteId);
|
||||
|
||||
// Create a manual deployment rule (type = 'deployment')
|
||||
$domain = $this->setupSiteDomain($siteId);
|
||||
$this->assertNotEmpty($domain);
|
||||
|
||||
// Create a redirect rule (type = 'redirect')
|
||||
$redirectDomain = \uniqid() . '-redirect-cleanup.custom.localhost';
|
||||
$redirectRule = $this->client->call(Client::METHOD_POST, '/proxy/rules/redirect', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'domain' => $redirectDomain,
|
||||
'url' => 'https://appwrite.io',
|
||||
'statusCode' => 301,
|
||||
'resourceType' => 'site',
|
||||
'resourceId' => $siteId,
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $redirectRule['headers']['status-code']);
|
||||
$this->assertNotEmpty($redirectRule['body']['$id']);
|
||||
|
||||
// Verify both rules exist (no type filter — catches all rule types)
|
||||
$rules = $this->client->call(Client::METHOD_GET, '/proxy/rules', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => [
|
||||
Query::equal('deploymentResourceId', [$siteId])->toString()
|
||||
]
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $rules['headers']['status-code']);
|
||||
$this->assertGreaterThanOrEqual(2, $rules['body']['total']);
|
||||
|
||||
// Delete the site
|
||||
$this->cleanupSite($siteId);
|
||||
|
||||
// Verify ALL rules (deployment + redirect) are cleaned up
|
||||
$this->assertEventually(function () use ($siteId) {
|
||||
$rules = $this->client->call(Client::METHOD_GET, '/proxy/rules', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => [
|
||||
Query::equal('deploymentResourceId', [$siteId])->toString()
|
||||
]
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $rules['headers']['status-code']);
|
||||
$this->assertEquals(0, $rules['body']['total']);
|
||||
}, 5000, 500);
|
||||
}
|
||||
|
||||
public function testGetFrameworks(): void
|
||||
{
|
||||
$frameworks = $this->client->call(Client::METHOD_GET, '/sites/frameworks', array_merge([
|
||||
|
||||
@@ -20,6 +20,7 @@ class ConfigTest extends TestCase
|
||||
$this->assertFalse($config->isLocal());
|
||||
$this->assertNull($config->getHostPath());
|
||||
$this->assertNull($config->getLockedDatabase());
|
||||
$this->assertEquals(['mongodb', 'mariadb'], $config->getEnabledDatabases());
|
||||
$this->assertEmpty($config->getVars());
|
||||
}
|
||||
|
||||
@@ -412,6 +413,7 @@ class ConfigTest extends TestCase
|
||||
'isLocal',
|
||||
'hostPath',
|
||||
'lockedDatabase',
|
||||
'enabledDatabases',
|
||||
];
|
||||
|
||||
foreach ($expectedKeys as $key) {
|
||||
@@ -435,6 +437,7 @@ class ConfigTest extends TestCase
|
||||
$this->assertFalse($array['isLocal']);
|
||||
$this->assertNull($array['hostPath']);
|
||||
$this->assertNull($array['lockedDatabase']);
|
||||
$this->assertEquals(['mongodb', 'mariadb'], $array['enabledDatabases']);
|
||||
}
|
||||
|
||||
public function testMultipleApplyCallsAccumulate(): void
|
||||
@@ -485,4 +488,67 @@ class ConfigTest extends TestCase
|
||||
$this->assertCount(1, $vars);
|
||||
$this->assertEquals('B', $vars[0]['name']);
|
||||
}
|
||||
|
||||
public function testDefaultEnabledDatabases(): void
|
||||
{
|
||||
$config = new Config();
|
||||
$this->assertEquals(['mongodb', 'mariadb'], $config->getEnabledDatabases());
|
||||
$this->assertTrue($config->isDatabaseEnabled('mongodb'));
|
||||
$this->assertTrue($config->isDatabaseEnabled('mariadb'));
|
||||
$this->assertFalse($config->isDatabaseEnabled('postgresql'));
|
||||
}
|
||||
|
||||
public function testSetEnabledDatabases(): void
|
||||
{
|
||||
$config = new Config();
|
||||
$config->setEnabledDatabases(['mongodb', 'mariadb', 'postgresql']);
|
||||
$this->assertEquals(['mongodb', 'mariadb', 'postgresql'], $config->getEnabledDatabases());
|
||||
$this->assertTrue($config->isDatabaseEnabled('postgresql'));
|
||||
}
|
||||
|
||||
public function testSetEnabledDatabasesFiltersInvalid(): void
|
||||
{
|
||||
$config = new Config();
|
||||
$config->setEnabledDatabases(['mongodb', '', null, 42, 'mariadb']);
|
||||
$this->assertEquals(['mongodb', 'mariadb'], $config->getEnabledDatabases());
|
||||
}
|
||||
|
||||
public function testSetEnabledDatabasesEmptyArrayPreservesExisting(): void
|
||||
{
|
||||
$config = new Config();
|
||||
$config->setEnabledDatabases(['mongodb', 'postgresql']);
|
||||
$config->setEnabledDatabases([]);
|
||||
$this->assertEquals(['mongodb', 'postgresql'], $config->getEnabledDatabases());
|
||||
}
|
||||
|
||||
public function testApplyEnabledDatabases(): void
|
||||
{
|
||||
$config = new Config();
|
||||
$config->apply(['enabledDatabases' => ['mongodb', 'mariadb', 'postgresql']]);
|
||||
$this->assertEquals(['mongodb', 'mariadb', 'postgresql'], $config->getEnabledDatabases());
|
||||
$this->assertTrue($config->isDatabaseEnabled('postgresql'));
|
||||
}
|
||||
|
||||
public function testApplyEnabledDatabasesNonArrayIgnored(): void
|
||||
{
|
||||
$config = new Config();
|
||||
$config->apply(['enabledDatabases' => 'mongodb']);
|
||||
$this->assertEquals(['mongodb', 'mariadb'], $config->getEnabledDatabases());
|
||||
}
|
||||
|
||||
public function testEnabledDatabasesInToArray(): void
|
||||
{
|
||||
$config = new Config();
|
||||
$config->setEnabledDatabases(['mongodb']);
|
||||
$array = $config->toArray();
|
||||
$this->assertEquals(['mongodb'], $array['enabledDatabases']);
|
||||
}
|
||||
|
||||
public function testEnabledDatabasesRoundTrip(): void
|
||||
{
|
||||
$config = new Config();
|
||||
$config->setEnabledDatabases(['mongodb', 'postgresql']);
|
||||
$rebuilt = new Config($config->toArray());
|
||||
$this->assertEquals(['mongodb', 'postgresql'], $rebuilt->getEnabledDatabases());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user