1
0
Fork 0
mirror of synced 2024-09-29 17:01:37 +13:00

Add unique url generator

This commit is contained in:
Matej Bačo 2023-02-22 16:07:34 +01:00
parent 0cf6a50c3c
commit 068acef0f6
8 changed files with 220 additions and 103 deletions

4
.env
View file

@ -13,10 +13,10 @@ _APP_SYSTEM_RESPONSE_FORMAT=
_APP_OPTIONS_ABUSE=disabled
_APP_OPTIONS_FORCE_HTTPS=disabled
_APP_OPENSSL_KEY_V1=your-secret-key
_APP_DOMAIN=demo.appwrite.io
_APP_DOMAIN=localhost
_APP_DOMAIN_FUNCTIONS=functions.localhost
_APP_DOMAIN_DO_TOKEN=
_APP_DOMAIN_TARGET=demo.appwrite.io
_APP_DOMAIN_TARGET=localhost
_APP_REDIS_HOST=redis
_APP_REDIS_PORT=6379
_APP_REDIS_PASS=

View file

@ -769,6 +769,96 @@ $collections = [
],
],
'routes' => [
'$collection' => ID::custom(Database::METADATA),
'$id' => ID::custom('routes'),
'name' => 'Routes',
'attributes' => [
[
'$id' => ID::custom('projectId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$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('domain'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('resourceType'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 100,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('resourceInternalId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('resourceId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
],
'indexes' => [
[
'$id' => ID::custom('_key_domain'),
'type' => Database::INDEX_UNIQUE,
'attributes' => ['domain'],
'lengths' => [Database::LENGTH_KEY],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_resource_id'),
'type' => Database::INDEX_KEY,
'attributes' => ['resourceInternalId'],
'lengths' => [Database::LENGTH_KEY],
'orders' => [Database::ORDER_ASC],
],
],
],
'schedules' => [
'$collection' => ID::custom(Database::METADATA),
'$id' => ID::custom('schedules'),

View file

@ -514,14 +514,14 @@ return [
'description' => 'The project key has expired. Please generate a new key using the Appwrite console.',
'code' => 401,
],
Exception::ROUTER_INVALID_URL => [
'name' => Exception::ROUTER_INVALID_URL,
'description' => 'Invalid preview URL for Appwrite Router.',
Exception::ROUTER_INVALID_TYPE => [
'name' => Exception::ROUTER_INVALID_TYPE,
'description' => 'Invalid domain configuration. Route type is not supported.',
'code' => 400,
],
Exception::ROUTER_CONSOLE_PROJECT => [
'name' => Exception::ROUTER_CONSOLE_PROJECT,
'description' => 'Use of "console" project with Appwrite Router is not allowed.',
Exception::ROUTER_UNKNOWN_HOST => [
'name' => Exception::ROUTER_UNKNOWN_HOST,
'description' => 'Host is not trusted. Add a custom domain to your project first.',
'code' => 400,
],
Exception::WEBHOOK_NOT_FOUND => [

View file

@ -38,7 +38,6 @@ use Utopia\Validator\Range;
use Utopia\Validator\WhiteList;
use Utopia\Config\Config;
use Executor\Executor;
use Selective\Base32\Base32;
use Utopia\CLI\Console;
use Utopia\Database\Validator\Roles;
use Utopia\Validator\Boolean;
@ -105,6 +104,19 @@ 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(),
]))
);
$function->setAttribute('scheduleId', $schedule->getId());
$function->setAttribute('scheduleInternalId', $schedule->getInternalId());
$dbForProject->updateDocument('functions', $function->getId(), $function);
@ -132,7 +144,8 @@ App::get('/v1/functions')
->inject('project')
->inject('response')
->inject('dbForProject')
->action(function (array $queries, string $search, Document $project, Response $response, Database $dbForProject) {
->inject('dbForConsole')
->action(function (array $queries, string $search, Document $project, Response $response, Database $dbForProject, Database $dbForConsole) {
$queries = Query::parseQueries($queries);
@ -157,15 +170,18 @@ App::get('/v1/functions')
$filterQueries = Query::groupByType($queries)['filters'];
$functionsDomain = App::getEnv('_APP_DOMAIN_FUNCTIONS');
$projectId = $project->getId();
$base32 = new Base32();
$projectIdHash = \rtrim($base32->encode($projectId), '=');
$functions = $dbForProject->find('functions', $queries);
$functions = \array_map(function (Document $function) use ($functionsDomain, $projectIdHash, $base32) {
$functionId = $function->getId();
$functionIdHash = \rtrim($base32->encode($functionId), '=');
$function = $function->setAttribute('url', "{$functionIdHash}-{$projectIdHash}.{$functionsDomain}");
$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);
@ -217,21 +233,24 @@ App::get('/v1/functions/:functionId')
->inject('project')
->inject('response')
->inject('dbForProject')
->action(function (string $functionId, Document $project, Response $response, Database $dbForProject) {
->inject('dbForConsole')
->action(function (string $functionId, Document $project, Response $response, Database $dbForProject, Database $dbForConsole) {
$function = $dbForProject->getDocument('functions', $functionId);
if ($function->isEmpty()) {
throw new Exception(Exception::FUNCTION_NOT_FOUND);
}
$functionsDomain = App::getEnv('_APP_DOMAIN_FUNCTIONS');
$projectId = $project->getId();
$functionId = $function->getId();
$base32 = new Base32();
$projectIdHash = \rtrim($base32->encode($projectId), '=');
$functionIdHash = \rtrim($base32->encode($functionId), '=');
$function = $function->setAttribute('url', "{$functionIdHash}-{$projectIdHash}.{$functionsDomain}");
$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);
});

View file

@ -37,7 +37,6 @@ use Appwrite\Utopia\Request\Filters\V12 as RequestV12;
use Appwrite\Utopia\Request\Filters\V13 as RequestV13;
use Appwrite\Utopia\Request\Filters\V14 as RequestV14;
use Appwrite\Utopia\Request\Filters\V15 as RequestV15;
use Selective\Base32\Base32;
use Utopia\Validator\Text;
use Utopia\Validator\WhiteList;
@ -65,30 +64,26 @@ App::init()
* Appwrite Router
*/
$host = $swooleRequest->header['host'] ?? '';
$mainDomain = App::getEnv('_APP_DOMAIN', '');
// Function Preview
if (\str_ends_with($host, App::getEnv('_APP_DOMAIN_FUNCTIONS'))) {
// Remove domain from host
$previewDomain = App::getEnv('_APP_DOMAIN_FUNCTIONS');
$host = \substr($host, 0, -1 * (1 + \strlen($previewDomain))); // +1 for starting dot (.)
// Only run Router when external domain
if($host !== $mainDomain) {
$route = Authorization::skip(
fn() => $dbForConsole->find('routes', [
Query::equal('domain', [$host]),
Query::limit(1)
])
)[0] ?? null;
if (empty($host) || \str_contains($host, '.')) {
throw new AppwriteException(AppwriteException::ROUTER_INVALID_URL);
if($route === null) {
throw new AppwriteException(AppwriteException::ROUTER_UNKNOWN_HOST);
}
$ids = explode('-', $host);
$type = $route->getAttribute('resourceType');
if (\count($ids) !== 2) {
throw new AppwriteException(AppwriteException::ROUTER_INVALID_URL);
}
// Active deployment preview
$functionIdHash = $ids[0];
$projectIdHash = $ids[1];
$base32 = new Base32();
$functionId = $base32->decode($functionIdHash);
$projectId = $base32->decode($projectIdHash);
if($type === 'function') {
$functionId = $route->getAttribute('resourceId');
$projectId = $route->getAttribute('projectId');
$body = \json_encode([
'async' => false,
@ -142,6 +137,9 @@ App::init()
}
return $response->setStatusCode($execution['statusCode'] ?? 200)->send($body);
} else {
throw new AppwriteException(AppwriteException::ROUTER_INVALID_TYPE);
}
}
/*

View file

@ -450,8 +450,19 @@ class DeletesV1 extends Worker
{
$projectId = $project->getId();
$dbForProject = $this->getProjectDB($project);
$dbForConsole = $this->getConsoleDB();
$functionId = $document->getId();
/**
* Delete routes
*/
Console::info("Deleting routes for function " . $functionId);
$this->deleteByGroup('routes', [
Query::equal('resourceType', ['function']),
Query::equal('resourceInternalId', [$document->getInternalId()]),
Query::equal('projectInternalId', [$project->getInternalId()])
], $dbForConsole);
/**
* Delete Variables
*/

View file

@ -73,8 +73,7 @@
"phpmailer/phpmailer": "6.6.0",
"chillerlan/php-qrcode": "4.3.3",
"adhocore/jwt": "1.1.2",
"slickdeals/statsd": "3.1.0",
"selective/base32": "^2.0"
"slickdeals/statsd": "3.1.0"
},
"repositories": [
{

View file

@ -164,8 +164,8 @@ class Exception extends \Exception
public const WEBHOOK_NOT_FOUND = 'webhook_not_found';
/** Router */
public const ROUTER_INVALID_URL = 'router_invalid_url';
public const ROUTER_CONSOLE_PROJECT = 'router_console_project';
public const ROUTER_UNKNOWN_HOST = 'router_unknown_host';
public const ROUTER_INVALID_TYPE = 'router_unknown_type';
/** Keys */
public const KEY_NOT_FOUND = 'key_not_found';