Further proxy API improvements

This commit is contained in:
Matej Bačo
2026-05-04 11:59:04 +02:00
parent cd6f5c64f0
commit 2231830826
9 changed files with 61 additions and 40 deletions
@@ -9,7 +9,6 @@ use Appwrite\Platform\Modules\Proxy\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Database\Validator\CustomId;
use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\Document;
@@ -45,7 +44,7 @@ class Create extends Action
->label('audits.resource', 'rule/{response.$id}')
->label('sdk', new Method(
namespace: 'proxy',
group: null,
group: 'rules',
name: 'createAPIRule',
description: <<<EOT
Create a new proxy rule for serving Appwrite's API on custom domain.
@@ -122,7 +121,7 @@ class Create extends Action
}
try {
$rule = $authorization->skip(fn() => $dbForPlatform->createDocument('rules', $rule));
$rule = $authorization->skip(fn () => $dbForPlatform->createDocument('rules', $rule));
} catch (Duplicate $e) {
throw new Exception(Exception::RULE_ALREADY_EXISTS);
}
@@ -140,6 +139,13 @@ class Create extends Action
$queueForEvents->setParam('ruleId', $rule->getId());
// Rename 'created' status to 'unverified' for consistency.
// 'verifying' and 'verified' statuses stay as is.
// 'unverified' in the meaning of failed certificate generation stays as is.
if ($rule->getAttribute('status') === 'created') {
$rule->setAttribute('status', 'unverified');
}
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($rule, Response::MODEL_PROXY_RULE);
@@ -39,7 +39,7 @@ class Delete extends Action
->label('audits.resource', 'rule/{request.ruleId}')
->label('sdk', new Method(
namespace: 'proxy',
group: null,
group: 'rules',
name: 'deleteRule',
description: <<<EOT
Delete a proxy rule by its unique ID.
@@ -59,7 +59,7 @@ class Delete extends Action
->inject('dbForPlatform')
->inject('queueForDeletes')
->inject('queueForEvents')
->inject('authorization')
->inject('authorization')
->callback($this->action(...));
}
@@ -72,13 +72,13 @@ class Delete extends Action
Event $queueForEvents,
Authorization $authorization,
) {
$rule = $authorization->skip(fn() => $dbForPlatform->getDocument('rules', $ruleId));
$rule = $authorization->skip(fn () => $dbForPlatform->getDocument('rules', $ruleId));
if ($rule->isEmpty() || $rule->getAttribute('projectInternalId') !== $project->getSequence()) {
throw new Exception(Exception::RULE_NOT_FOUND);
}
$authorization->skip(fn() => $dbForPlatform->deleteDocument('rules', $rule->getId()));
$authorization->skip(fn () => $dbForPlatform->deleteDocument('rules', $rule->getId()));
$queueForDeletes
->setType(DELETE_TYPE_DOCUMENT)
@@ -35,7 +35,7 @@ class Get extends Action
->label('scope', 'rules.read')
->label('sdk', new Method(
namespace: 'proxy',
group: null,
group: 'rules',
name: 'getRule',
description: <<<EOT
Get a proxy rule by its unique ID.
@@ -63,13 +63,13 @@ class Get extends Action
Database $dbForPlatform,
Authorization $authorization,
) {
$rule = $authorization->skip(fn() => $dbForPlatform->getDocument('rules', $ruleId));
$rule = $authorization->skip(fn () => $dbForPlatform->getDocument('rules', $ruleId));
if ($rule->isEmpty() || $rule->getAttribute('projectInternalId') !== $project->getSequence()) {
throw new Exception(Exception::RULE_NOT_FOUND);
}
$certificate = $authorization->skip(fn() => $dbForPlatform->getDocument('certificates', $rule->getAttribute('certificateId', '')));
$certificate = $authorization->skip(fn () => $dbForPlatform->getDocument('certificates', $rule->getAttribute('certificateId', '')));
// Give priority to certificate generation logs if present
if (!empty($certificate->getAttribute('logs', ''))) {
@@ -78,6 +78,13 @@ class Get extends Action
$rule->setAttribute('renewAt', $certificate->getAttribute('renewDate', ''));
// Rename 'created' status to 'unverified' for consistency.
// 'verifying' and 'verified' statuses stay as is.
// 'unverified' in the meaning of failed certificate generation stays as is.
if ($rule->getAttribute('status') === 'created') {
$rule->setAttribute('status', 'unverified');
}
$response->dynamic($rule, Response::MODEL_PROXY_RULE);
}
}
@@ -1,6 +1,6 @@
<?php
namespace Appwrite\Platform\Modules\Proxy\Http\Rules\Verification;
namespace Appwrite\Platform\Modules\Proxy\Http\Rules\Status;
use Appwrite\Event\Event;
use Appwrite\Event\Publisher\Certificate;
@@ -33,8 +33,9 @@ class Update extends Action
$this
->setHttpMethod(Action::HTTP_REQUEST_METHOD_PATCH)
->setHttpPath('/v1/proxy/rules/:ruleId/verification')
->desc('Update rule verification status')
->setHttpPath('/v1/proxy/rules/:ruleId/status')
->httpAlias('/v1/proxy/rules/:ruleId/verification')
->desc('Update rule status')
->groups(['api', 'proxy'])
->label('scope', 'rules.write')
->label('event', 'rules.[ruleId].update')
@@ -42,8 +43,8 @@ class Update extends Action
->label('audits.resource', 'rule/{response.$id}')
->label('sdk', new Method(
namespace: 'proxy',
group: null,
name: 'updateRuleVerification',
group: 'rules',
name: 'updateRuleStatus',
description: <<<EOT
If not succeeded yet, retry verification process of a proxy rule domain. This endpoint triggers domain verification by checking DNS records. If verification is successful, a TLS certificate will be automatically provisioned for the domain asynchronously in the background.
EOT,
@@ -76,7 +77,7 @@ class Update extends Action
Log $log,
Authorization $authorization,
) {
$rule = $authorization->skip(fn() => $dbForPlatform->getDocument('rules', $ruleId));
$rule = $authorization->skip(fn () => $dbForPlatform->getDocument('rules', $ruleId));
if ($rule->isEmpty() || $rule->getAttribute('projectInternalId') !== $project->getSequence()) {
throw new Exception(Exception::RULE_NOT_FOUND);
@@ -93,7 +94,7 @@ class Update extends Action
try {
$this->verifyRule($rule, $log);
// Reset logs and status for the rule
$rule = $authorization->skip(fn() => $dbForPlatform->updateDocument('rules', $rule->getId(), new Document([
$rule = $authorization->skip(fn () => $dbForPlatform->updateDocument('rules', $rule->getId(), new Document([
'logs' => '',
'status' => RULE_STATUS_CERTIFICATE_GENERATING,
])));
@@ -101,12 +102,12 @@ class Update extends Action
$certificateId = $rule->getAttribute('certificateId', '');
// Reset logs for the associated certificate.
if (!empty($certificateId)) {
$certificate = $authorization->skip(fn() => $dbForPlatform->updateDocument('certificates', $certificateId, new Document([
$certificate = $authorization->skip(fn () => $dbForPlatform->updateDocument('certificates', $certificateId, new Document([
'logs' => '',
])));
}
} catch (Exception $err) {
$authorization->skip(fn() => $dbForPlatform->updateDocument('rules', $rule->getId(), new Document([
$authorization->skip(fn () => $dbForPlatform->updateDocument('rules', $rule->getId(), new Document([
'$updatedAt' => DateTime::now(),
])));
throw $err;
@@ -40,7 +40,7 @@ class XList extends Action
->label('scope', 'rules.read')
->label('sdk', new Method(
namespace: 'proxy',
group: null,
group: 'rules',
name: 'listRules',
description: <<<EOT
Get a list of all the proxy rules. You can use the query params to filter your results.
@@ -59,7 +59,7 @@ class XList extends Action
->inject('response')
->inject('project')
->inject('dbForPlatform')
->inject('authorization')
->inject('authorization')
->callback($this->action(...));
}
@@ -94,7 +94,7 @@ class XList extends Action
}
$ruleId = $cursor->getValue();
$cursorDocument = $authorization->skip(fn() => $dbForPlatform->getDocument('rules', $ruleId));
$cursorDocument = $authorization->skip(fn () => $dbForPlatform->getDocument('rules', $ruleId));
if ($cursorDocument->isEmpty()) {
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Rule '{$ruleId}' for the 'cursor' value not found.");
@@ -105,9 +105,9 @@ class XList extends Action
$filterQueries = Query::groupByType($queries)['filters'];
$rules = $authorization->skip(fn() => $dbForPlatform->find('rules', $queries));
$rules = $authorization->skip(fn () => $dbForPlatform->find('rules', $queries));
foreach ($rules as $rule) {
$certificate = $authorization->skip(fn() => $dbForPlatform->getDocument('certificates', $rule->getAttribute('certificateId', '')));
$certificate = $authorization->skip(fn () => $dbForPlatform->getDocument('certificates', $rule->getAttribute('certificateId', '')));
// Give priority to certificate generation logs if present
if (!empty($certificate->getAttribute('logs', ''))) {
@@ -115,11 +115,18 @@ class XList extends Action
}
$rule->setAttribute('renewAt', $certificate->getAttribute('renewDate', ''));
// Rename 'created' status to 'unverified' for consistency.
// 'verifying' and 'verified' statuses stay as is.
// 'unverified' in the meaning of failed certificate generation stays as is.
if ($rule->getAttribute('status') === 'created') {
$rule->setAttribute('status', 'unverified');
}
}
$response->dynamic(new Document([
'rules' => $rules,
'total' => $total ? $authorization->skip(fn() => $dbForPlatform->count('rules', $filterQueries, APP_LIMIT_COUNT)) : 0,
'total' => $total ? $authorization->skip(fn () => $dbForPlatform->count('rules', $filterQueries, APP_LIMIT_COUNT)) : 0,
]), Response::MODEL_PROXY_RULE_LIST);
}
}
@@ -8,7 +8,7 @@ use Appwrite\Platform\Modules\Proxy\Http\Rules\Function\Create as CreateFunction
use Appwrite\Platform\Modules\Proxy\Http\Rules\Get as GetRule;
use Appwrite\Platform\Modules\Proxy\Http\Rules\Redirect\Create as CreateRedirectRule;
use Appwrite\Platform\Modules\Proxy\Http\Rules\Site\Create as CreateSiteRule;
use Appwrite\Platform\Modules\Proxy\Http\Rules\Verification\Update as UpdateRuleVerification;
use Appwrite\Platform\Modules\Proxy\Http\Rules\Status\Update as UpdateRuleStatus;
use Appwrite\Platform\Modules\Proxy\Http\Rules\XList as ListRules;
use Utopia\Platform\Service;
@@ -26,6 +26,6 @@ class Http extends Service
$this->addAction(GetRule::getName(), new GetRule());
$this->addAction(ListRules::getName(), new ListRules());
$this->addAction(DeleteRule::getName(), new DeleteRule());
$this->addAction(UpdateRuleVerification::getName(), new UpdateRuleVerification());
$this->addAction(UpdateRuleStatus::getName(), new UpdateRuleStatus());
}
}
+4 -4
View File
@@ -74,7 +74,7 @@ class Rule extends Model
])
->addRule('deploymentResourceId', [
'type' => self::TYPE_STRING,
'description' => 'ID deployment\'s resource. Used if type is "deployment"',
'description' => 'ID of deployment\'s resource (site or function ID). Used if type is "deployment"',
'default' => '',
'example' => 'n3u9feiwmf',
])
@@ -86,10 +86,10 @@ class Rule extends Model
])
->addRule('status', [
'type' => self::TYPE_ENUM,
'description' => 'Domain verification status. Possible values are "created", "verifying", "verified" and "unverified"',
'default' => 'created',
'description' => 'Domain verification status. Possible values are "unverified", "verifying", "verified"',
'default' => 'unverified',
'example' => 'verified',
'enum' => ['created', 'verifying', 'verified', 'unverified'],
'enum' => ['unverified', 'verifying', 'verified'],
])
->addRule('logs', [
'type' => self::TYPE_STRING,
+7 -7
View File
@@ -425,7 +425,7 @@ trait ProxyBase
$ruleId = $rule['body']['$id'];
$rule = $this->updateRuleVerification($ruleId);
$rule = $this->updateRuleStatus($ruleId);
$this->assertEquals(400, $rule['headers']['status-code']);
$this->cleanupRule($ruleId);
@@ -599,7 +599,7 @@ trait ProxyBase
$this->assertNotEmpty($rule['body']['$id']);
$ruleId = $rule['body']['$id'];
$rule = $this->updateRuleVerification($ruleId);
$rule = $this->updateRuleStatus($ruleId);
$this->assertEquals(200, $rule['headers']['status-code']);
$this->assertEquals($ruleId, $rule['body']['$id']);
$this->assertEquals('verifying', $rule['body']['status']);
@@ -633,7 +633,7 @@ trait ProxyBase
$this->assertStringContainsString('is missing CNAME record', $rule['body']['logs']);
$ruleId = $rule['body']['$id'];
$rule = $this->updateRuleVerification($ruleId);
$rule = $this->updateRuleStatus($ruleId);
$this->assertEquals(400, $rule['headers']['status-code']);
$this->assertStringContainsString('is missing CNAME record', $rule['body']['message']);
@@ -666,7 +666,7 @@ trait ProxyBase
$this->assertStringContainsString('is missing CNAME record', $rule['body']['logs']);
$ruleId = $rule['body']['$id'];
$rule = $this->updateRuleVerification($ruleId);
$rule = $this->updateRuleStatus($ruleId);
$this->assertEquals(400, $rule['headers']['status-code']);
$this->assertStringContainsString('is missing CNAME record', $rule['body']['message']);
@@ -683,7 +683,7 @@ trait ProxyBase
$this->assertStringContainsString('has incorrect CNAME value', $rule['body']['logs']);
$ruleId = $rule['body']['$id'];
$rule = $this->updateRuleVerification($ruleId);
$rule = $this->updateRuleStatus($ruleId);
$this->assertEquals(400, $rule['headers']['status-code']);
$this->assertStringContainsString('has incorrect CNAME value', $rule['body']['message']);
@@ -700,7 +700,7 @@ trait ProxyBase
$this->assertStringContainsString('has incorrect CAA value', $rule['body']['logs']);
$ruleId = $rule['body']['$id'];
$rule = $this->updateRuleVerification($ruleId);
$rule = $this->updateRuleStatus($ruleId);
$this->assertEquals(400, $rule['headers']['status-code']);
$this->assertStringContainsString('has incorrect CAA value', $rule['body']['message']);
@@ -734,7 +734,7 @@ trait ProxyBase
sleep(1);
$updatedRule = $this->updateRuleVerification($ruleId);
$updatedRule = $this->updateRuleStatus($ruleId);
$this->assertEquals(400, $updatedRule['headers']['status-code']);
$this->assertStringContainsString($initiallogs, $updatedRule['body']['message']);
+2 -2
View File
@@ -34,9 +34,9 @@ trait ProxyHelpers
return $rule;
}
protected function updateRuleVerification(string $ruleId): mixed
protected function updateRuleStatus(string $ruleId): mixed
{
$rule = $this->client->call(Client::METHOD_PATCH, '/proxy/rules/' . $ruleId . '/verification', array_merge([
$rule = $this->client->call(Client::METHOD_PATCH, '/proxy/rules/' . $ruleId . '/status', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), []);