Implement rules

This commit is contained in:
Matej Bačo
2023-03-08 19:30:01 +01:00
parent f223e8b948
commit 59f5978fa3
32 changed files with 500 additions and 568 deletions
+84 -121
View File
@@ -721,17 +721,6 @@ $collections = [
'array' => false,
'filters' => ['subQueryKeys'],
],
[
'$id' => ID::custom('domains'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 16384,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => ['subQueryDomains'],
],
[
'$id' => ID::custom('search'),
'type' => Database::VAR_STRING,
@@ -769,10 +758,10 @@ $collections = [
],
],
'routes' => [
'rules' => [
'$collection' => ID::custom(Database::METADATA),
'$id' => ID::custom('routes'),
'name' => 'Routes',
'$id' => ID::custom('rules'),
'name' => 'Rules',
'attributes' => [
[
'$id' => ID::custom('projectId'),
@@ -791,7 +780,7 @@ $collections = [
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
@@ -840,6 +829,50 @@ $collections = [
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('redirect'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 16384,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('verification'),
'type' => Database::VAR_BOOLEAN,
'format' => '',
'size' => 0,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('certificateId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('search'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 16384,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
]
],
'indexes' => [
[
@@ -850,12 +883,47 @@ $collections = [
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_resource_id'),
'$id' => ID::custom('_key_resource_internal_id'),
'type' => Database::INDEX_KEY,
'attributes' => ['resourceInternalId'],
'lengths' => [Database::LENGTH_KEY],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_resource_id'),
'type' => Database::INDEX_KEY,
'attributes' => ['resourceId'],
'lengths' => [],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_resource_type'),
'type' => Database::INDEX_KEY,
'attributes' => ['resourceType'],
'lengths' => [],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_redirect'),
'type' => Database::INDEX_KEY,
'attributes' => ['redirect'],
'lengths' => [],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_verification'),
'type' => Database::INDEX_KEY,
'attributes' => ['verification'],
'lengths' => [],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_search'),
'type' => Database::INDEX_FULLTEXT,
'attributes' => ['search'],
'lengths' => [],
'orders' => [],
]
],
],
@@ -1065,111 +1133,6 @@ $collections = [
],
],
'domains' => [
'$collection' => ID::custom(Database::METADATA),
'$id' => ID::custom('domains'),
'name' => 'domains',
'attributes' => [
[
'$id' => ID::custom('projectInternalId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('projectId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('updated'),
'type' => Database::VAR_DATETIME,
'format' => '',
'size' => 0,
'signed' => false,
'required' => false,
'default' => null,
'array' => false,
'filters' => ['datetime'],
],
[
'$id' => ID::custom('domain'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('tld'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('registerable'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('verification'),
'type' => Database::VAR_BOOLEAN,
'format' => '',
'size' => 0,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('certificateId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
],
'indexes' => [
[
'$id' => ID::custom('_key_project'),
'type' => Database::INDEX_KEY,
'attributes' => ['projectInternalId'],
'lengths' => [Database::LENGTH_KEY],
'orders' => [Database::ORDER_ASC],
],
],
],
'keys' => [
'$collection' => ID::custom(Database::METADATA),
'$id' => ID::custom('keys'),
+25 -15
View File
@@ -524,6 +524,31 @@ return [
'description' => 'Host is not trusted. Add a custom domain to your project first.',
'code' => 400,
],
Exception::RULE_RESOURCE_ID_MISSING => [
'name' => Exception::RULE_RESOURCE_ID_MISSING,
'description' => 'With resourceType you provided, the resourceId is required.',
'code' => 400,
],
Exception::RULE_RESOURCE_ID_NOT_FOUND => [
'name' => Exception::RULE_RESOURCE_ID_NOT_FOUND,
'description' => 'Resource could not be found. Check resourceId and resourceType.',
'code' => 404,
],
Exception::RULE_NOT_FOUND => [
'name' => Exception::RULE_NOT_FOUND,
'description' => 'Rule with the requested ID could not be found.',
'code' => 404,
],
Exception::RULE_ALREADY_EXISTS => [
'name' => Exception::RULE_ALREADY_EXISTS,
'description' => 'A rule with the requested domain already exists.',
'code' => 409,
],
Exception::RULE_VERIFICATION_FAILED => [
'name' => Exception::RULE_VERIFICATION_FAILED,
'description' => 'Rule verification for it\'s domain has failed.',
'code' => 401,
],
Exception::WEBHOOK_NOT_FOUND => [
'name' => Exception::WEBHOOK_NOT_FOUND,
'description' => 'Webhook with the requested ID could not be found.',
@@ -539,16 +564,6 @@ return [
'description' => 'Platform with the requested ID could not be found.',
'code' => 404,
],
Exception::DOMAIN_NOT_FOUND => [
'name' => Exception::DOMAIN_NOT_FOUND,
'description' => 'Domain with the requested ID could not be found.',
'code' => 404,
],
Exception::DOMAIN_ALREADY_EXISTS => [
'name' => Exception::DOMAIN_ALREADY_EXISTS,
'description' => 'A Domain with the requested ID already exists.',
'code' => 409,
],
Exception::VARIABLE_NOT_FOUND => [
'name' => Exception::VARIABLE_NOT_FOUND,
'description' => 'Variable with the requested ID could not be found.',
@@ -559,9 +574,4 @@ return [
'description' => 'Variable with the same ID already exists in your project.',
'code' => 409,
],
Exception::DOMAIN_VERIFICATION_FAILED => [
'name' => Exception::DOMAIN_VERIFICATION_FAILED,
'description' => 'Domain verification for the requested domain has failed.',
'code' => 401,
],
];
+1
View File
@@ -49,6 +49,7 @@ $admins = [
'functions.write',
'execution.read',
'execution.write',
'rules.read'
];
return [
+6
View File
@@ -76,4 +76,10 @@ return [ // List of publicly visible scopes
'health.read' => [
'description' => 'Access to read your project\'s health status',
],
'rules.read' => [
'description' => 'Access to read your project\'s proxy rules',
],
'rules.write' => [
'description' => 'Access to create, update, and delete your project\'s proxy rules',
],
];
+13
View File
@@ -173,6 +173,19 @@ return [
'optional' => true,
'icon' => '/images/services/functions.png',
],
'proxy' => [
'key' => 'proxy',
'name' => 'Proxy',
'subtitle' => 'The Proxy Service allows you configure actions for your domains beyond DNS configuration.',
'description' => '/docs/services/proxy.md',
'controller' => 'api/proxy.php',
'sdk' => true,
'docs' => true,
'docsUrl' => 'https://appwrite.io/docs/proxy',
'tests' => false,
'optional' => true,
'icon' => '/images/services/proxy.png',
],
'mock' => [
'key' => 'mock',
'name' => 'Mock',
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+27 -45
View File
@@ -106,18 +106,30 @@ App::post('/v1/functions')
]))
);
$functionsDomain = App::getEnv('_APP_DOMAIN_FUNCTIONS');
$routeSubdomain = ID::unique();
$route = Authorization::skip(
fn() => $dbForConsole->createDocument('routes', new Document([
'projectId' => $project->getId(),
'projectInternalId' => $project->getInternalId(),
'domain' => "{$routeSubdomain}.{$functionsDomain}",
'resourceType' => 'function',
'resourceId' => $function->getId(),
'resourceInternalId' => $function->getInternalId(),
]))
);
$functionsDomain = App::getEnv('_APP_DOMAIN_FUNCTIONS', 'disabled');
if($functionsDomain !== 'disabled') {
$ruleId = ID::unique();
$routeSubdomain = ID::unique();
$domain = "{$routeSubdomain}.{$functionsDomain}";
$route = Authorization::skip(
fn() => $dbForConsole->createDocument('rules', new Document([
'$id' => $ruleId,
'projectId' => $project->getId(),
'projectInternalId' => $project->getInternalId(),
'domain' => $domain,
'resourceType' => 'function',
'resourceId' => $function->getId(),
'resourceInternalId' => $function->getInternalId(),
'redirect' => '',
'verification' => true,
'certificateId' => '',
'search' => implode(' ', [ $domain, $ruleId, $function->getId(), 'function' ]),
]))
);
// TODO: Probably trigger event for rules.create
}
$function->setAttribute('scheduleId', $schedule->getId());
$function->setAttribute('scheduleInternalId', $schedule->getInternalId());
@@ -143,11 +155,9 @@ App::get('/v1/functions')
->label('sdk.response.model', Response::MODEL_FUNCTION_LIST)
->param('queries', [], new Functions(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Functions::ALLOWED_ATTRIBUTES), true)
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
->inject('project')
->inject('response')
->inject('dbForProject')
->inject('dbForConsole')
->action(function (array $queries, string $search, Document $project, Response $response, Database $dbForProject, Database $dbForConsole) {
->action(function (array $queries, string $search, Response $response, Database $dbForProject) {
$queries = Query::parseQueries($queries);
@@ -172,23 +182,8 @@ App::get('/v1/functions')
$filterQueries = Query::groupByType($queries)['filters'];
$functions = $dbForProject->find('functions', $queries);
$functions = \array_map(function (Document $function) use ($dbForConsole, $project) {
$route = Authorization::skip(
fn() => $dbForConsole->find('routes', [
Query::equal('resourceType', ['function']),
Query::equal('resourceInternalId', [ $function->getInternalId() ]),
Query::equal('projectInternalId', [ $project->getInternalId() ]),
Query::limit(1),
Query::orderAsc('_createdAt')
])
)[0] ?? null;
$function = $function->setAttribute('url', $route ? $route->getAttribute('domain') : '');
return $function;
}, $functions);
$response->dynamic(new Document([
'functions' => $functions,
'functions' => $dbForProject->find('functions', $queries),
'total' => $dbForProject->count('functions', $filterQueries, APP_LIMIT_COUNT),
]), Response::MODEL_FUNCTION_LIST);
});
@@ -232,28 +227,15 @@ App::get('/v1/functions/:functionId')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_FUNCTION)
->param('functionId', '', new UID(), 'Function ID.')
->inject('project')
->inject('response')
->inject('dbForProject')
->inject('dbForConsole')
->action(function (string $functionId, Document $project, Response $response, Database $dbForProject, Database $dbForConsole) {
->action(function (string $functionId, Response $response, Database $dbForProject) {
$function = $dbForProject->getDocument('functions', $functionId);
if ($function->isEmpty()) {
throw new Exception(Exception::FUNCTION_NOT_FOUND);
}
$route = Authorization::skip(
fn() => $dbForConsole->find('routes', [
Query::equal('resourceType', ['function']),
Query::equal('resourceInternalId', [ $function->getInternalId() ]),
Query::equal('projectInternalId', [ $project->getInternalId() ]),
Query::limit(1),
Query::orderAsc('_createdAt')
])
)[0] ?? null;
$function = $function->setAttribute('url', $route ? $route->getAttribute('domain') : '');
$response->dynamic($function, Response::MODEL_FUNCTION);
});
+1 -241
View File
@@ -2,11 +2,8 @@
use Appwrite\Auth\Auth;
use Appwrite\Auth\Validator\Password;
use Appwrite\Event\Certificate;
use Appwrite\Event\Delete;
use Appwrite\Event\Validator\Event;
use Appwrite\Network\Validator\CNAME;
use Appwrite\Network\Validator\Domain as DomainValidator;
use Appwrite\Network\Validator\Origin;
use Appwrite\Network\Validator\URL;
use Appwrite\Utopia\Database\Validator\CustomId;
@@ -24,7 +21,6 @@ use Utopia\Database\Query;
use Utopia\Database\Role;
use Utopia\Database\Validator\DatetimeValidator;
use Utopia\Database\Validator\UID;
use Utopia\Domains\Domain;
use Appwrite\Extend\Exception;
use Appwrite\Utopia\Database\Validator\Queries\Projects;
use Utopia\Cache\Cache;
@@ -124,7 +120,6 @@ App::post('/v1/projects')
'authProviders' => [],
'webhooks' => null,
'keys' => null,
'domains' => null,
'auths' => $auths,
'search' => implode(' ', [$projectId, $name]),
'database' => $database,
@@ -1165,239 +1160,4 @@ App::delete('/v1/projects/:projectId/platforms/:platformId')
$dbForConsole->deleteCachedDocument('projects', $project->getId());
$response->noContent();
});
// Domains
App::post('/v1/projects/:projectId/domains')
->desc('Create Domain')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
->label('sdk.method', 'createDomain')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_DOMAIN)
->param('projectId', '', new UID(), 'Project unique ID.')
->param('domain', null, new DomainValidator(), 'Domain name.')
->inject('response')
->inject('dbForConsole')
->action(function (string $projectId, string $domain, Response $response, Database $dbForConsole) {
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
throw new Exception(Exception::PROJECT_NOT_FOUND);
}
$document = $dbForConsole->findOne('domains', [
Query::equal('domain', [$domain]),
Query::equal('projectInternalId', [$project->getInternalId()]),
]);
if ($document && !$document->isEmpty()) {
throw new Exception(Exception::DOMAIN_ALREADY_EXISTS);
}
$target = new Domain(App::getEnv('_APP_DOMAIN_TARGET', ''));
if (!$target->isKnown() || $target->isTest()) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Unreachable CNAME target (' . $target->get() . '), please use a domain with a public suffix.');
}
$domain = new Domain($domain);
$domain = new Document([
'$id' => ID::unique(),
'$permissions' => [
Permission::read(Role::any()),
Permission::update(Role::any()),
Permission::delete(Role::any()),
],
'projectInternalId' => $project->getInternalId(),
'projectId' => $project->getId(),
'updated' => DateTime::now(),
'domain' => $domain->get(),
'tld' => $domain->getSuffix(),
'registerable' => $domain->getRegisterable(),
'verification' => false,
'certificateId' => null,
]);
$domain = $dbForConsole->createDocument('domains', $domain);
$dbForConsole->deleteCachedDocument('projects', $project->getId());
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($domain, Response::MODEL_DOMAIN);
});
App::get('/v1/projects/:projectId/domains')
->desc('List Domains')
->groups(['api', 'projects'])
->label('scope', 'projects.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
->label('sdk.method', 'listDomains')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_DOMAIN_LIST)
->param('projectId', '', new UID(), 'Project unique ID.')
->inject('response')
->inject('dbForConsole')
->action(function (string $projectId, Response $response, Database $dbForConsole) {
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
throw new Exception(Exception::PROJECT_NOT_FOUND);
}
$domains = $dbForConsole->find('domains', [
Query::equal('projectInternalId', [$project->getInternalId()]),
Query::limit(5000),
]);
$response->dynamic(new Document([
'domains' => $domains,
'total' => count($domains),
]), Response::MODEL_DOMAIN_LIST);
});
App::get('/v1/projects/:projectId/domains/:domainId')
->desc('Get Domain')
->groups(['api', 'projects'])
->label('scope', 'projects.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
->label('sdk.method', 'getDomain')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_DOMAIN)
->param('projectId', '', new UID(), 'Project unique ID.')
->param('domainId', '', new UID(), 'Domain unique ID.')
->inject('response')
->inject('dbForConsole')
->action(function (string $projectId, string $domainId, Response $response, Database $dbForConsole) {
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
throw new Exception(Exception::PROJECT_NOT_FOUND);
}
$domain = $dbForConsole->findOne('domains', [
Query::equal('_uid', [$domainId]),
Query::equal('projectInternalId', [$project->getInternalId()]),
]);
if ($domain === false || $domain->isEmpty()) {
throw new Exception(Exception::DOMAIN_NOT_FOUND);
}
$response->dynamic($domain, Response::MODEL_DOMAIN);
});
App::patch('/v1/projects/:projectId/domains/:domainId/verification')
->desc('Update Domain Verification Status')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
->label('sdk.method', 'updateDomainVerification')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_DOMAIN)
->param('projectId', '', new UID(), 'Project unique ID.')
->param('domainId', '', new UID(), 'Domain unique ID.')
->inject('response')
->inject('dbForConsole')
->action(function (string $projectId, string $domainId, Response $response, Database $dbForConsole) {
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
throw new Exception(Exception::PROJECT_NOT_FOUND);
}
$domain = $dbForConsole->findOne('domains', [
Query::equal('_uid', [$domainId]),
Query::equal('projectInternalId', [$project->getInternalId()]),
]);
if ($domain === false || $domain->isEmpty()) {
throw new Exception(Exception::DOMAIN_NOT_FOUND);
}
$target = new Domain(App::getEnv('_APP_DOMAIN_TARGET', ''));
if (!$target->isKnown() || $target->isTest()) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Unreachable CNAME target (' . $target->get() . '), please use a domain with a public suffix.');
}
if ($domain->getAttribute('verification') === true) {
return $response->dynamic($domain, Response::MODEL_DOMAIN);
}
$validator = new CNAME($target->get()); // Verify Domain with DNS records
if (!$validator->isValid($domain->getAttribute('domain', ''))) {
throw new Exception(Exception::DOMAIN_VERIFICATION_FAILED);
}
$dbForConsole->updateDocument('domains', $domain->getId(), $domain->setAttribute('verification', true));
$dbForConsole->deleteCachedDocument('projects', $project->getId());
// Issue a TLS certificate when domain is verified
$event = new Certificate();
$event
->setDomain($domain)
->trigger();
$response->dynamic($domain, Response::MODEL_DOMAIN);
});
App::delete('/v1/projects/:projectId/domains/:domainId')
->desc('Delete Domain')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
->label('sdk.method', 'deleteDomain')
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
->label('sdk.response.model', Response::MODEL_NONE)
->param('projectId', '', new UID(), 'Project unique ID.')
->param('domainId', '', new UID(), 'Domain unique ID.')
->inject('response')
->inject('dbForConsole')
->inject('deletes')
->action(function (string $projectId, string $domainId, Response $response, Database $dbForConsole, Delete $deletes) {
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
throw new Exception(Exception::PROJECT_NOT_FOUND);
}
$domain = $dbForConsole->findOne('domains', [
Query::equal('_uid', [$domainId]),
Query::equal('projectInternalId', [$project->getInternalId()]),
]);
if ($domain === false || $domain->isEmpty()) {
throw new Exception(Exception::DOMAIN_NOT_FOUND);
}
$dbForConsole->deleteDocument('domains', $domain->getId());
$dbForConsole->deleteCachedDocument('projects', $project->getId());
$deletes
->setType(DELETE_TYPE_CERTIFICATES)
->setDocument($domain);
$response->noContent();
});
});
+264
View File
@@ -0,0 +1,264 @@
<?php
use Appwrite\Event\Certificate;
use Appwrite\Event\Delete;
use Appwrite\Event\Event;
use Appwrite\Extend\Exception;
use Appwrite\ID;
use Appwrite\Network\Validator\CNAME;
use Appwrite\Network\Validator\Domain as DomainValidator;
use Appwrite\Network\Validator\URL;
use Appwrite\Utopia\Database\Validator\Queries\Rules;
use Appwrite\Utopia\Response;
use Utopia\App;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Query;
use Utopia\Database\Validator\UID;
use Utopia\Domains\Domain;
use Utopia\Validator\Text;
use Utopia\Validator\WhiteList;
// TODO: Certificate generation
App::post('/v1/proxy/rules')
->groups(['api', 'proxy'])
->desc('Create Rule')
->label('scope', 'rules.write')
->label('event', 'rules.[ruleId].create')
->label('audits.event', 'rule.create')
->label('audits.resource', 'rule/{response.$id}')
->label('sdk.auth', [APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'proxy')
->label('sdk.method', 'createRule')
->label('sdk.description', '/docs/references/proxy/create-rule.md')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_PROXY_RULE)
->param('domain', null, new DomainValidator(), 'Domain name.')
->param('resourceType', null, new WhiteList(['api', 'function', 'redirect']), 'Action definition for the rule. Possible values are "api", "function", or "redirect"')
->param('resourceId', '', new UID(), 'ID of resource for the action type. If resourceType is "api" or "url", leave empty. If resourceType is "function", provide ID of the function.', true)
->param('redirect', '', new URL(), 'Redirect URL for redirect action. Only provide if resourceType is "redirect"', true)
->inject('response')
->inject('project')
->inject('events')
->inject('dbForConsole')
->inject('dbForProject')
->action(function (string $domain, string $resourceType, string $resourceId, string $redirect, Response $response, Document $project, Event $eventsInstance, Database $dbForConsole, Database $dbForProject) {
$document = $dbForConsole->findOne('rules', [
Query::equal('domain', [$domain]),
]);
if ($document && !$document->isEmpty()) {
throw new Exception(Exception::RULE_ALREADY_EXISTS);
}
$target = new Domain(App::getEnv('_APP_DOMAIN_TARGET', ''));
if (!$target->isKnown() || $target->isTest()) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Unreachable CNAME target (' . $target->get() . '), please use a domain with a public suffix.');
}
$resourceInternalId = '';
if($resourceType == 'function') {
if(empty($resourceId)) {
throw new Exception(Exception::RULE_RESOURCE_ID_MISSING);
}
$function = $dbForProject->getDocument('functions', $resourceId);
if ($function->isEmpty()) {
throw new Exception(Exception::RULE_RESOURCE_ID_NOT_FOUND);
}
$resourceInternalId = $function->getInternalId();
}
$domain = new Domain($domain);
$ruleId = ID::unique();
$rule = $dbForConsole->createDocument('rules', new Document([
'$id' => $ruleId,
'projectId' => $project->getId(),
'projectInternalId' => $project->getInternalId(),
'domain' => $domain->get(),
'resourceType' => $resourceType,
'resourceId' => $resourceId,
'resourceInternalId' => $resourceInternalId,
'redirect' => $redirect,
'verification' => false,
'certificateId' => '',
'search' => implode(' ', [ $domain, $ruleId, $resourceId, $resourceType, $redirect ]),
]));
$eventsInstance->setParam('ruleId', $rule->getId());
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($rule, Response::MODEL_RULE);
});
App::get('/v1/proxy/rules')
->groups(['api', 'proxy'])
->desc('List Rules')
->label('scope', 'rules.read')
->label('sdk.auth', [APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'proxy')
->label('sdk.method', 'listRules')
->label('sdk.description', '/docs/references/proxy/list-rules.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_PROXY_RULE_LIST)
->param('queries', [], new Rules(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Rules::ALLOWED_ATTRIBUTES), true)
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
->inject('response')
->inject('project')
->inject('dbForConsole')
->action(function (array $queries, string $search, Response $response, Document $project, Database $dbForConsole) {
$queries = Query::parseQueries($queries);
if (!empty($search)) {
$queries[] = Query::search('search', $search);
}
$queries[] = Query::equal('projectInternalId', [$project->getInternalId()]);
// Get cursor document if there was a cursor query
$cursor = Query::getByType($queries, Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE);
$cursor = reset($cursor);
if ($cursor) {
/** @var Query $cursor */
$ruleId = $cursor->getValue();
$cursorDocument = $dbForConsole->getDocument('rules', $ruleId);
if ($cursorDocument->isEmpty()) {
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Rule '{$ruleId}' for the 'cursor' value not found.");
}
$cursor->setValue($cursorDocument);
}
$filterQueries = Query::groupByType($queries)['filters'];
$response->dynamic(new Document([
'rules' => $dbForConsole->find('rules', $queries),
'total' => $dbForConsole->count('rules', $filterQueries, APP_LIMIT_COUNT),
]), Response::MODEL_PROXY_RULE_LIST);
});
App::get('/v1/proxy/rules/:ruleId')
->groups(['api', 'proxy'])
->desc('Get Rule')
->label('scope', 'rules.read')
->label('sdk.auth', [APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'proxy')
->label('sdk.method', 'getRule')
->label('sdk.description', '/docs/references/proxy/get-rule.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_PROXY_RULE)
->param('ruleId', '', new UID(), 'Rule ID.')
->inject('response')
->inject('project')
->inject('dbForConsole')
->action(function (string $ruleId, Response $response, Document $project, Database $dbForConsole) {
$rule = $dbForConsole->getDocument('rule', $ruleId);
if ($rule->isEmpty() || $rule->getAttribute('projectInternalId') !== $project->getInternalId()) {
throw new Exception(Exception::RULE_NOT_FOUND);
}
$response->dynamic($rule, Response::MODEL_PROXY_RULE);
});
App::delete('/v1/proxy/rules/:ruleId')
->groups(['api', 'proxy'])
->desc('Delete Rule')
->label('scope', 'rules.write')
->label('event', 'rules.[ruleId].delete')
->label('audits.event', 'function.delete')
->label('audits.resource', 'rule/{request.ruleId}')
->label('sdk.auth', [APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'proxy')
->label('sdk.method', 'deleteRule')
->label('sdk.description', '/docs/references/proxy/delete-rule.md')
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
->label('sdk.response.model', Response::MODEL_NONE)
->param('ruleId', '', new UID(), 'Rule ID.')
->inject('response')
->inject('project')
->inject('dbForConsole')
->inject('deletes')
->inject('events')
->action(function (string $ruleId, Response $response, Document $project, Database $dbForConsole, Delete $deletes, Event $events) {
$rule = $dbForConsole->getDocument('rules', $ruleId);
if ($rule->isEmpty() || $rule->getAttribute('projectInternalId') !== $project->getInternalId()) {
throw new Exception(Exception::RULE_NOT_FOUND);
}
if (!$dbForConsole->deleteDocument('rules', $rule->getId())) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove rule from DB');
}
// TODO: Ensure certificate is deleted. Previously DELETE_TYPE_CERTIFICATES
$deletes
->setType(DELETE_TYPE_DOCUMENT)
->setDocument($rule);
$events->setParam('ruleId', $rule->getId());
$response->noContent();
});
App::patch('/v1/proxy/rules/:ruleId/verification')
->desc('Update Rule Verification Status')
->groups(['api', 'proxy'])
->label('scope', 'rules.write')
->label('sdk.auth', [APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'proxy')
->label('sdk.method', 'updateRuleVerification')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_PROXY_RULE)
->param('ruleId', '', new UID(), 'Rule ID.')
->inject('response')
->inject('project')
->inject('dbForConsole')
->action(function (string $ruleId, Response $response, Document $project, Database $dbForConsole) {
$rule = $dbForConsole->getDocument('rules', $ruleId);
if ($rule->isEmpty() || $rule->getAttribute('projectInternalId') !== $project->getInternalId()) {
throw new Exception(Exception::RULE_NOT_FOUND);
}
$target = new Domain(App::getEnv('_APP_DOMAIN_TARGET', ''));
if (!$target->isKnown() || $target->isTest()) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Unreachable CNAME target (' . $target->get() . '), please use a domain with a public suffix.');
}
if ($rule->getAttribute('verification') === true) {
return $response->dynamic($rule, Response::MODEL_PROXY_RULE);
}
$validator = new CNAME($target->get()); // Verify Domain with DNS records
if (!$validator->isValid($rule->getAttribute('domain', ''))) {
throw new Exception(Exception::RULE_VERIFICATION_FAILED);
}
$dbForConsole->updateDocument('rules', $rule->getId(), $rule->setAttribute('verification', true));
// Issue a TLS certificate when domain is verified
$event = new Certificate();
$event
->setDomain(new Document([
'domain' => $rule->getAttribute('domain')
]))
->trigger();
$response->dynamic($rule, Response::MODEL_PROXY_RULE);
});
+1 -55
View File
@@ -45,7 +45,7 @@ Config::setParam('cookieSamesite', Response::COOKIE_SAMESITE_NONE);
function router(Database $dbForConsole, string $host, SwooleRequest $swooleRequest, Response $response) {
$route = Authorization::skip(
fn() => $dbForConsole->find('routes', [
fn() => $dbForConsole->find('rules', [
Query::equal('domain', [$host]),
Query::limit(1)
])
@@ -188,60 +188,6 @@ App::init()
Request::setFilter(null);
}
$domain = $request->getHostname();
$domains = Config::getParam('domains', []);
if (!array_key_exists($domain, $domains)) {
$domain = new Domain(!empty($domain) ? $domain : '');
if (empty($domain->get()) || !$domain->isKnown() || $domain->isTest()) {
$domains[$domain->get()] = false;
Console::warning($domain->get() . ' is not a publicly accessible domain. Skipping SSL certificate generation.');
} elseif (str_starts_with($request->getURI(), '/.well-known/acme-challenge')) {
Console::warning('Skipping SSL certificates generation on ACME challenge.');
} else {
Authorization::disable();
$envDomain = App::getEnv('_APP_DOMAIN', '');
$mainDomain = null;
if (!empty($envDomain) && $envDomain !== 'localhost') {
$mainDomain = $envDomain;
} else {
$domainDocument = $dbForConsole->findOne('domains', [Query::orderAsc('_id')]);
$mainDomain = $domainDocument ? $domainDocument->getAttribute('domain') : $domain->get();
}
if ($mainDomain !== $domain->get()) {
Console::warning($domain->get() . ' is not a main domain. Skipping SSL certificate generation.');
} else {
$domainDocument = $dbForConsole->findOne('domains', [
Query::equal('domain', [$domain->get()])
]);
if (!$domainDocument) {
$domainDocument = new Document([
'domain' => $domain->get(),
'tld' => $domain->getSuffix(),
'registerable' => $domain->getRegisterable(),
'verification' => false,
'certificateId' => null,
]);
$domainDocument = $dbForConsole->createDocument('domains', $domainDocument);
Console::info('Issuing a TLS certificate for the main domain (' . $domain->get() . ') in a few seconds...');
(new Certificate())
->setDomain($domainDocument)
->trigger();
}
}
$domains[$domain->get()] = true;
Authorization::reset(); // ensure authorization is re-enabled
}
Config::setParam('domains', $domains);
}
$localeParam = (string) $request->getParam('locale', $request->getHeader('x-appwrite-locale', ''));
if (\in_array($localeParam, Config::getParam('locale-codes'))) {
$locale->setDefault($localeParam);
-15
View File
@@ -149,7 +149,6 @@ const DELETE_TYPE_TEAMS = 'teams';
const DELETE_TYPE_EXECUTIONS = 'executions';
const DELETE_TYPE_AUDIT = 'audit';
const DELETE_TYPE_ABUSE = 'abuse';
const DELETE_TYPE_CERTIFICATES = 'certificates';
const DELETE_TYPE_USAGE = 'usage';
const DELETE_TYPE_REALTIME = 'realtime';
const DELETE_TYPE_BUCKETS = 'buckets';
@@ -322,20 +321,6 @@ Database::addFilter(
}
);
Database::addFilter(
'subQueryDomains',
function (mixed $value) {
return null;
},
function (mixed $value, Document $document, Database $database) {
return $database
->find('domains', [
Query::equal('projectInternalId', [$document->getInternalId()]),
Query::limit(APP_LIMIT_SUBQUERY),
]);
}
);
Database::addFilter(
'subQueryKeys',
function (mixed $value) {
+6 -16
View File
@@ -12,6 +12,8 @@ use Utopia\Database\ID;
use Utopia\Database\Query;
use Utopia\Domains\Domain;
// TODO: Update with rules collection
require_once __DIR__ . '/../init.php';
Console::title('Certificates V1 Worker');
@@ -182,11 +184,6 @@ class CertificatesV1 extends Worker
$envDomain = App::getEnv('_APP_DOMAIN', '');
if (!empty($envDomain) && $envDomain !== 'localhost') {
return $envDomain;
} else {
$domainDocument = $this->dbForConsole->findOne('domains', [Query::orderAsc('_id')]);
if ($domainDocument) {
return $domainDocument->getAttribute('domain');
}
}
return null;
@@ -403,20 +400,13 @@ class CertificatesV1 extends Worker
*/
private function updateDomainDocuments(string $certificateId, string $domain): void
{
$domains = $this->dbForConsole->find('domains', [
$rule = $this->dbForConsole->findOne('rules', [
Query::equal('domain', [$domain]),
Query::limit(1000),
]);
foreach ($domains as $domainDocument) {
$domainDocument->setAttribute('updated', DateTime::now());
$domainDocument->setAttribute('certificateId', $certificateId);
$this->dbForConsole->updateDocument('domains', $domainDocument->getId(), $domainDocument);
if ($domainDocument->getAttribute('projectId')) {
$this->dbForConsole->deleteCachedDocument('projects', $domainDocument->getAttribute('projectId'));
}
if(!$rule->isEmpty()) {
$rule->setAttribute('certificateId', $certificateId);
$this->dbForConsole->updateDocument('rules', $rule->getId(), $rule);
}
}
}
+3 -7
View File
@@ -101,11 +101,6 @@ class DeletesV1 extends Worker
$this->deleteExpiredSessions();
break;
case DELETE_TYPE_CERTIFICATES:
$document = new Document($this->args['document']);
$this->deleteCertificates($document);
break;
case DELETE_TYPE_USAGE:
$this->deleteUsageStats($this->args['hourlyUsageRetentionDatetime']);
break;
@@ -457,7 +452,7 @@ class DeletesV1 extends Worker
* Delete routes
*/
Console::info("Deleting routes for function " . $functionId);
$this->deleteByGroup('routes', [
$this->deleteByGroup('rules', [
Query::equal('resourceType', ['function']),
Query::equal('resourceInternalId', [$document->getInternalId()]),
Query::equal('projectInternalId', [$project->getInternalId()])
@@ -686,6 +681,7 @@ class DeletesV1 extends Worker
Console::info("Listed {$count} document by group in " . ($executionEnd - $executionStart) . " seconds");
}
// TODO: Update to new routes delete action
/**
* @param Document $document certificates document
*/
@@ -695,7 +691,7 @@ class DeletesV1 extends Worker
// If domain has certificate generated
if (isset($document['certificateId'])) {
$domainUsingCertificate = $consoleDB->findOne('domains', [
$domainUsingCertificate = $consoleDB->findOne('rules', [
Query::equal('certificateId', [$document['certificateId']])
]);
+1
View File
@@ -0,0 +1 @@
Create a new proxy rule.
+1
View File
@@ -0,0 +1 @@
Delete a proxyrule by its unique ID.
+1
View File
@@ -0,0 +1 @@
Get a proxy rule by its unique ID.
+1
View File
@@ -0,0 +1 @@
Get a list of all the proxy rules. You can use the query params to filter your results.
+1
View File
@@ -0,0 +1 @@
TODO: Write
+7 -5
View File
@@ -167,6 +167,13 @@ class Exception extends \Exception
public const ROUTER_UNKNOWN_HOST = 'router_unknown_host';
public const ROUTER_INVALID_TYPE = 'router_unknown_type';
/** Proxy */
public const RULE_RESOURCE_ID_MISSING = 'rule_resource_id_missing';
public const RULE_RESOURCE_ID_NOT_FOUND = 'rule_resource_id_not_found';
public const RULE_NOT_FOUND = 'rule_not_found';
public const RULE_ALREADY_EXISTS = 'rule_already_exists';
public const RULE_VERIFICATION_FAILED = 'rule_verification_failed';
/** Keys */
public const KEY_NOT_FOUND = 'key_not_found';
@@ -177,11 +184,6 @@ class Exception extends \Exception
/** Platform */
public const PLATFORM_NOT_FOUND = 'platform_not_found';
/** Domain */
public const DOMAIN_NOT_FOUND = 'domain_not_found';
public const DOMAIN_ALREADY_EXISTS = 'domain_already_exists';
public const DOMAIN_VERIFICATION_FAILED = 'domain_verification_failed';
protected $type = '';
public function __construct(string $type = Exception::GENERAL_UNKNOWN, string $message = null, int $code = null, \Throwable $previous = null)
@@ -2,7 +2,6 @@
namespace Appwrite\Platform\Tasks;
use Appwrite\Auth\Auth;
use Appwrite\Event\Certificate;
use Appwrite\Event\Delete;
use Utopia\App;
@@ -0,0 +1,22 @@
<?php
namespace Appwrite\Utopia\Database\Validator\Queries;
class Rules extends Base
{
public const ALLOWED_ATTRIBUTES = [
'domain',
'resourceType',
'resourceId',
'url'
];
/**
* Expression constructor
*
*/
public function __construct()
{
parent::__construct('rules', self::ALLOWED_ATTRIBUTES);
}
}
+6 -2
View File
@@ -190,6 +190,10 @@ class Response extends SwooleResponse
public const MODEL_FUNC_PERMISSIONS = 'funcPermissions';
public const MODEL_HEADERS = 'headers';
// Proxy
public const MODEL_PROXY_RULE = 'proxyRule';
public const MODEL_PROXY_RULE_LIST = 'proxyRuleList';
// Project
public const MODEL_PROJECT = 'project';
public const MODEL_PROJECT_LIST = 'projectList';
@@ -267,7 +271,6 @@ class Response extends SwooleResponse
->setModel(new BaseList('API Keys List', self::MODEL_KEY_LIST, 'keys', self::MODEL_KEY, true, false))
->setModel(new BaseList('Providers List', self::MODEL_PROVIDER_LIST, 'platforms', self::MODEL_PROVIDER, true, false))
->setModel(new BaseList('Platforms List', self::MODEL_PLATFORM_LIST, 'platforms', self::MODEL_PLATFORM, true, false))
->setModel(new BaseList('Domains List', self::MODEL_DOMAIN_LIST, 'domains', self::MODEL_DOMAIN, true, false))
->setModel(new BaseList('Countries List', self::MODEL_COUNTRY_LIST, 'countries', self::MODEL_COUNTRY))
->setModel(new BaseList('Continents List', self::MODEL_CONTINENT_LIST, 'continents', self::MODEL_CONTINENT))
->setModel(new BaseList('Languages List', self::MODEL_LANGUAGE_LIST, 'languages', self::MODEL_LANGUAGE))
@@ -276,6 +279,7 @@ class Response extends SwooleResponse
->setModel(new BaseList('Metric List', self::MODEL_METRIC_LIST, 'metrics', self::MODEL_METRIC, true, false))
->setModel(new BaseList('Variables List', self::MODEL_VARIABLE_LIST, 'variables', self::MODEL_VARIABLE))
->setModel(new BaseList('Status List', self::MODEL_HEALTH_STATUS_LIST, 'statuses', self::MODEL_HEALTH_STATUS))
->setModel(new BaseList('Rule List', self::MODEL_PROXY_RULE_LIST, 'rules', self::MODEL_PROXY_RULE))
// Entities
->setModel(new Database())
->setModel(new Collection())
@@ -319,7 +323,6 @@ class Response extends SwooleResponse
->setModel(new Project())
->setModel(new Webhook())
->setModel(new Key())
->setModel(new Domain())
->setModel(new Provider())
->setModel(new Platform())
->setModel(new Variable())
@@ -344,6 +347,7 @@ class Response extends SwooleResponse
->setModel(new UsageFunction())
->setModel(new UsageProject())
->setModel(new Headers())
->setModel(new Rule())
// Verification
// Recovery
// Tests (keep last)
@@ -34,7 +34,7 @@ class BaseList extends Model
$namesWithCap = [
'documents', 'collections', 'users', 'files', 'buckets', 'functions',
'deployments', 'executions', 'projects', 'webhooks', 'keys',
'platforms', 'domains', 'memberships', 'teams'
'platforms', 'rules', 'memberships', 'teams'
];
if (\in_array($name, $namesWithCap)) {
@@ -43,12 +43,6 @@ class Func extends Model
'default' => '',
'example' => 'My Function',
])
->addRule('url', [
'type' => self::TYPE_STRING,
'description' => 'Function URL to trigger execution.',
'default' => '',
'example' => 'functions.localhost',
])
->addRule('enabled', [
'type' => self::TYPE_BOOLEAN,
'description' => 'Function enabled.',
@@ -142,13 +142,6 @@ class Project extends Model
'example' => new \stdClass(),
'array' => true,
])
->addRule('domains', [
'type' => Response::MODEL_DOMAIN,
'description' => 'List of Domains.',
'default' => [],
'example' => new \stdClass(),
'array' => true,
])
;
$services = Config::getParam('services', []);
@@ -4,32 +4,29 @@ namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model;
use stdClass;
use Utopia\Database\Document;
class Domain extends Model
class Rule extends Model
{
/**
* @var bool
*/
protected bool $public = false;
public function __construct()
{
$this
->addRule('$id', [
'type' => self::TYPE_STRING,
'description' => 'Domain ID.',
'description' => 'Function ID.',
'default' => '',
'example' => '5e5ea5c16897e',
])
->addRule('$createdAt', [
'type' => self::TYPE_DATETIME,
'description' => 'Domain creation date in ISO 8601 format.',
'description' => 'Function creation date in ISO 8601 format.',
'default' => '',
'example' => self::TYPE_DATETIME_EXAMPLE,
])
->addRule('$updatedAt', [
'type' => self::TYPE_DATETIME,
'description' => 'Domain update date in ISO 8601 format.',
'description' => 'Function update date in ISO 8601 format.',
'default' => '',
'example' => self::TYPE_DATETIME_EXAMPLE,
])
@@ -39,30 +36,30 @@ class Domain extends Model
'default' => '',
'example' => 'appwrite.company.com',
])
->addRule('registerable', [
->addRule('resourceType', [
'type' => self::TYPE_STRING,
'description' => 'Registerable domain name.',
'description' => 'Action definition for the rule. Possible values are "api", "function", or "redirect"',
'default' => '',
'example' => 'company.com',
'example' => 'function',
])
->addRule('tld', [
->addRule('resourceId', [
'type' => self::TYPE_STRING,
'description' => 'TLD name.',
'description' => 'ID of resource for the action type. If resourceType is "api" or "url", leave empty. If resourceType is "function", provide ID of the function.',
'default' => '',
'example' => 'com',
'example' => 'myAwesomeFunction',
])
->addRule('redirect', [
'type' => self::TYPE_STRING,
'description' => 'Redirect URL for redirect action. Only provide if resourceType is "redirect"',
'default' => '',
'example' => 'https://appwrite.io/',
])
->addRule('verification', [
'type' => self::TYPE_BOOLEAN,
'description' => 'Verification process status.',
'description' => 'Domain verification status.',
'default' => false,
'example' => true,
])
->addRule('certificateId', [
'type' => self::TYPE_STRING,
'description' => 'Certificate ID.',
'default' => '',
'example' => '6ejea5c13377e',
])
;
}
@@ -73,7 +70,7 @@ class Domain extends Model
*/
public function getName(): string
{
return 'Domain';
return 'Rule';
}
/**
@@ -83,6 +80,6 @@ class Domain extends Model
*/
public function getType(): string
{
return Response::MODEL_DOMAIN;
return Response::MODEL_PROXY_RULE;
}
}