WIP
This commit is contained in:
parent
95e835a902
commit
db345fae41
|
@ -61,6 +61,7 @@ $collections = [
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
'collections' => [
|
'collections' => [
|
||||||
'$collection' => ID::custom('databases'),
|
'$collection' => ID::custom('databases'),
|
||||||
'$id' => ID::custom('collections'),
|
'$id' => ID::custom('collections'),
|
||||||
|
@ -3234,6 +3235,7 @@ $collections = [
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
|
|
||||||
'cache' => [
|
'cache' => [
|
||||||
'$collection' => Database::METADATA,
|
'$collection' => Database::METADATA,
|
||||||
'$id' => 'cache',
|
'$id' => 'cache',
|
||||||
|
@ -3290,6 +3292,7 @@ $collections = [
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
'files' => [
|
'files' => [
|
||||||
'$collection' => ID::custom('buckets'),
|
'$collection' => ID::custom('buckets'),
|
||||||
'$id' => ID::custom('files'),
|
'$id' => ID::custom('files'),
|
||||||
|
|
|
@ -631,6 +631,8 @@ App::get('/.well-known/acme-challenge')
|
||||||
});
|
});
|
||||||
|
|
||||||
include_once __DIR__ . '/shared/api.php';
|
include_once __DIR__ . '/shared/api.php';
|
||||||
|
include_once __DIR__ . '/shared/api/auth.php';
|
||||||
|
include_once __DIR__ . '/shared/api/cache.php';
|
||||||
|
|
||||||
foreach (Config::getParam('services', []) as $service) {
|
foreach (Config::getParam('services', []) as $service) {
|
||||||
include_once $service['controller'];
|
include_once $service['controller'];
|
||||||
|
|
|
@ -7,6 +7,7 @@ use Appwrite\Event\Delete;
|
||||||
use Appwrite\Event\Event;
|
use Appwrite\Event\Event;
|
||||||
use Appwrite\Event\Func;
|
use Appwrite\Event\Func;
|
||||||
use Appwrite\Event\Mail;
|
use Appwrite\Event\Mail;
|
||||||
|
use Appwrite\Event\Usage;
|
||||||
use Appwrite\Messaging\Adapter\Realtime;
|
use Appwrite\Messaging\Adapter\Realtime;
|
||||||
use Appwrite\Utopia\Response;
|
use Appwrite\Utopia\Response;
|
||||||
use Appwrite\Utopia\Request;
|
use Appwrite\Utopia\Request;
|
||||||
|
@ -47,25 +48,64 @@ $parseLabel = function (string $label, array $responsePayload, array $requestPar
|
||||||
return $label;
|
return $label;
|
||||||
};
|
};
|
||||||
|
|
||||||
$databaseListener = function (string $event, Document $document) {
|
$databaseListener = function (string $event, Document $document, Document $project, Usage $usage) {
|
||||||
$multiplier = 1;
|
$value = 1;
|
||||||
|
|
||||||
if ($event === Database::EVENT_DOCUMENT_DELETE) {
|
if ($event === Database::EVENT_DOCUMENT_DELETE) {
|
||||||
$multiplier = -1;
|
$value = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
$collection = $document->getCollection();
|
switch ($document->getCollection()) {
|
||||||
switch ($collection) {
|
|
||||||
case 'users':
|
case 'users':
|
||||||
// $usage->setParam('users.{scope}.count.total', 1 * $multiplier);
|
$usage->addMetric("{$project->getId()}", "users", $value); // per project
|
||||||
|
break;
|
||||||
|
case 'teams':
|
||||||
|
$usage->addMetric("{$project->getId()}", "teams", $value); // per project
|
||||||
|
break;
|
||||||
|
case 'sessions':
|
||||||
|
$usage->addMetric("{$project->getId()}", "sessions", $value); // per project
|
||||||
break;
|
break;
|
||||||
case 'databases':
|
case 'databases':
|
||||||
// $usage->setParam('databases.{scope}.count.total', 1 * $multiplier);
|
$usage->addMetric("{$project->getId()}", "databases", $value); // per project
|
||||||
|
break;
|
||||||
|
case 'collections':
|
||||||
|
$usage->addMetric("{$project->getId()}.[DATABASE_ID]", "collections", $value); // per database
|
||||||
|
$usage->addMetric("{$project->getId()}", "collections", $value); // per project
|
||||||
|
break;
|
||||||
|
case 'documents':
|
||||||
|
$usage->addMetric("{$project->getId()}.[DATABASE_ID].[COLLECTION_ID]", "documents", $value); // per collection
|
||||||
|
$usage->addMetric("{$project->getId()}.[DATABASE_ID]", "documents", $value); // per database
|
||||||
|
$usage->addMetric("{$project->getId()}", "documents", $value); // per project
|
||||||
break;
|
break;
|
||||||
case 'buckets':
|
case 'buckets':
|
||||||
// $usage->setParam('buckets.{scope}.count.total', 1 * $multiplier);
|
$usage->addMetric("{$project->getId()}", "buckets", $value); // per project
|
||||||
|
break;
|
||||||
|
case 'files':
|
||||||
|
$usage->addMetric("{$project->getId()}.[BUCKET_ID]", "files", $value); // per bucket
|
||||||
|
$usage->addMetric("{$project->getId()}.[BUCKET_ID]", "files.storage", $document->getAttribute('sizeOriginal') * $value); // per bucket
|
||||||
|
$usage->addMetric("{$project->getId()}", "files", $value); // per project
|
||||||
|
$usage->addMetric("{$project->getId()}", "files.storage", $document->getAttribute('sizeOriginal') * $value); // per project
|
||||||
|
break;
|
||||||
|
case 'functions':
|
||||||
|
$usage->addMetric("{$project->getId()}", "functions", $value); // per project
|
||||||
break;
|
break;
|
||||||
case 'deployments':
|
case 'deployments':
|
||||||
// $usage->setParam('deployments.{scope}.storage.size', $document->getAttribute('size') * $multiplier);
|
$usage->addMetric("{$project->getId()}.[FUNCTION_ID]", "deployments", $value); // per function
|
||||||
|
$usage->addMetric("{$project->getId()}.[FUNCTION_ID]", "deployments.storage", $document->getAttribute('size') * $value); // per function
|
||||||
|
$usage->addMetric("{$project->getId()}", "deployments", $value); // per project
|
||||||
|
$usage->addMetric("{$project->getId()}", "deployments.storage", $document->getAttribute('size') * $value); // per project
|
||||||
|
break;
|
||||||
|
case 'builds':
|
||||||
|
$usage->addMetric("{$project->getId()}.[FUNCTION_ID]", "builds", $value); // per function
|
||||||
|
$usage->addMetric("{$project->getId()}.[FUNCTION_ID]", "builds.storage", $document->getAttribute('size') * $value); // per function
|
||||||
|
$usage->addMetric("{$project->getId()}", "builds", $value); // per project
|
||||||
|
$usage->addMetric("{$project->getId()}", "builds.storage", $document->getAttribute('size') * $value); // per project
|
||||||
|
break;
|
||||||
|
case 'executions':
|
||||||
|
$usage->addMetric("{$project->getId()}.[FUNCTION_ID]", "executions", $value); // per function
|
||||||
|
$usage->addMetric("{$project->getId()}.[FUNCTION_ID]", "executions.compute", $document->getAttribute('duration') * $value); // per function
|
||||||
|
$usage->addMetric("{$project->getId()}", "executions", $value); // per project
|
||||||
|
$usage->addMetric("{$project->getId()}", "executions.compute", $document->getAttribute('duration') * $value); // per project
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
// if (strpos($collection, 'bucket_') === 0) {
|
// if (strpos($collection, 'bucket_') === 0) {
|
||||||
|
@ -121,11 +161,11 @@ App::init()
|
||||||
foreach ($abuseKeyLabel as $abuseKey) {
|
foreach ($abuseKeyLabel as $abuseKey) {
|
||||||
$timeLimit = new TimeLimit($abuseKey, $route->getLabel('abuse-limit', 0), $route->getLabel('abuse-time', 3600), $dbForProject);
|
$timeLimit = new TimeLimit($abuseKey, $route->getLabel('abuse-limit', 0), $route->getLabel('abuse-time', 3600), $dbForProject);
|
||||||
$timeLimit
|
$timeLimit
|
||||||
->setParam('{userId}', $user->getId())
|
->setParam('{userId}', $user->getId())
|
||||||
->setParam('{userAgent}', $request->getUserAgent(''))
|
->setParam('{userAgent}', $request->getUserAgent(''))
|
||||||
->setParam('{ip}', $request->getIP())
|
->setParam('{ip}', $request->getIP())
|
||||||
->setParam('{url}', $request->getHostname() . $route->getPath())
|
->setParam('{url}', $request->getHostname() . $route->getPath())
|
||||||
->setParam('{method}', $request->getMethod());
|
->setParam('{method}', $request->getMethod());
|
||||||
$timeLimitArray[] = $timeLimit;
|
$timeLimitArray[] = $timeLimit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -199,9 +239,10 @@ App::init()
|
||||||
$deletes->setProject($project);
|
$deletes->setProject($project);
|
||||||
$database->setProject($project);
|
$database->setProject($project);
|
||||||
|
|
||||||
$dbForProject->on(Database::EVENT_DOCUMENT_CREATE, fn ($event, Document $document) => $databaseListener($event, $document, $usage));
|
$dbForProject
|
||||||
|
->on(Database::EVENT_DOCUMENT_CREATE, fn ($event, Document $document) => $databaseListener($event, $document))
|
||||||
$dbForProject->on(Database::EVENT_DOCUMENT_DELETE, fn ($event, Document $document) => $databaseListener($event, $document, $usage));
|
->on(Database::EVENT_DOCUMENT_DELETE, fn ($event, Document $document) => $databaseListener($event, $document))
|
||||||
|
;
|
||||||
|
|
||||||
$useCache = $route->getLabel('cache', false);
|
$useCache = $route->getLabel('cache', false);
|
||||||
|
|
||||||
|
@ -262,60 +303,6 @@ App::init()
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
App::init()
|
|
||||||
->groups(['auth'])
|
|
||||||
->inject('utopia')
|
|
||||||
->inject('request')
|
|
||||||
->inject('project')
|
|
||||||
->action(function (App $utopia, Request $request, Document $project) {
|
|
||||||
|
|
||||||
$route = $utopia->match($request);
|
|
||||||
|
|
||||||
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
|
|
||||||
$isAppUser = Auth::isAppUser(Authorization::getRoles());
|
|
||||||
|
|
||||||
if ($isAppUser || $isPrivilegedUser) { // Skip limits for app and console devs
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$auths = $project->getAttribute('auths', []);
|
|
||||||
switch ($route->getLabel('auth.type', '')) {
|
|
||||||
case 'emailPassword':
|
|
||||||
if (($auths['emailPassword'] ?? true) === false) {
|
|
||||||
throw new Exception(Exception::USER_AUTH_METHOD_UNSUPPORTED, 'Email / Password authentication is disabled for this project');
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'magic-url':
|
|
||||||
if ($project->getAttribute('usersAuthMagicURL', true) === false) {
|
|
||||||
throw new Exception(Exception::USER_AUTH_METHOD_UNSUPPORTED, 'Magic URL authentication is disabled for this project');
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'anonymous':
|
|
||||||
if (($auths['anonymous'] ?? true) === false) {
|
|
||||||
throw new Exception(Exception::USER_AUTH_METHOD_UNSUPPORTED, 'Anonymous authentication is disabled for this project');
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'invites':
|
|
||||||
if (($auths['invites'] ?? true) === false) {
|
|
||||||
throw new Exception(Exception::USER_AUTH_METHOD_UNSUPPORTED, 'Invites authentication is disabled for this project');
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'jwt':
|
|
||||||
if (($auths['JWT'] ?? true) === false) {
|
|
||||||
throw new Exception(Exception::USER_AUTH_METHOD_UNSUPPORTED, 'JWT authentication is disabled for this project');
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new Exception(Exception::USER_AUTH_METHOD_UNSUPPORTED, 'Unsupported authentication route');
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
App::shutdown()
|
App::shutdown()
|
||||||
->groups(['api'])
|
->groups(['api'])
|
||||||
->inject('utopia')
|
->inject('utopia')
|
||||||
|
@ -336,6 +323,7 @@ App::shutdown()
|
||||||
if (empty($events->getPayload())) {
|
if (empty($events->getPayload())) {
|
||||||
$events->setPayload($responsePayload);
|
$events->setPayload($responsePayload);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Trigger functions.
|
* Trigger functions.
|
||||||
*/
|
*/
|
||||||
|
|
62
app/controllers/shared/api/auth.php
Normal file
62
app/controllers/shared/api/auth.php
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Appwrite\Auth\Auth;
|
||||||
|
use Appwrite\Utopia\Request;
|
||||||
|
use Utopia\App;
|
||||||
|
use Appwrite\Extend\Exception;
|
||||||
|
use Utopia\Database\Document;
|
||||||
|
use Utopia\Database\Validator\Authorization;
|
||||||
|
|
||||||
|
App::init()
|
||||||
|
->groups(['auth'])
|
||||||
|
->inject('utopia')
|
||||||
|
->inject('request')
|
||||||
|
->inject('project')
|
||||||
|
->action(function (App $utopia, Request $request, Document $project) {
|
||||||
|
|
||||||
|
$route = $utopia->match($request);
|
||||||
|
|
||||||
|
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
|
||||||
|
$isAppUser = Auth::isAppUser(Authorization::getRoles());
|
||||||
|
|
||||||
|
if ($isAppUser || $isPrivilegedUser) { // Skip limits for app and console devs
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$auths = $project->getAttribute('auths', []);
|
||||||
|
switch ($route->getLabel('auth.type', '')) {
|
||||||
|
case 'emailPassword':
|
||||||
|
if (($auths['emailPassword'] ?? true) === false) {
|
||||||
|
throw new Exception(Exception::USER_AUTH_METHOD_UNSUPPORTED, 'Email / Password authentication is disabled for this project');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'magic-url':
|
||||||
|
if ($project->getAttribute('usersAuthMagicURL', true) === false) {
|
||||||
|
throw new Exception(Exception::USER_AUTH_METHOD_UNSUPPORTED, 'Magic URL authentication is disabled for this project');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'anonymous':
|
||||||
|
if (($auths['anonymous'] ?? true) === false) {
|
||||||
|
throw new Exception(Exception::USER_AUTH_METHOD_UNSUPPORTED, 'Anonymous authentication is disabled for this project');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'invites':
|
||||||
|
if (($auths['invites'] ?? true) === false) {
|
||||||
|
throw new Exception(Exception::USER_AUTH_METHOD_UNSUPPORTED, 'Invites authentication is disabled for this project');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'jwt':
|
||||||
|
if (($auths['JWT'] ?? true) === false) {
|
||||||
|
throw new Exception(Exception::USER_AUTH_METHOD_UNSUPPORTED, 'JWT authentication is disabled for this project');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Exception(Exception::USER_AUTH_METHOD_UNSUPPORTED, 'Unsupported authentication route');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
53
src/Appwrite/Event/Usage.php
Normal file
53
src/Appwrite/Event/Usage.php
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Appwrite\Event;
|
||||||
|
|
||||||
|
use Utopia\Queue\Client;
|
||||||
|
use Utopia\Queue\Connection;
|
||||||
|
|
||||||
|
class Usage extends Event
|
||||||
|
{
|
||||||
|
protected array $metrics = [];
|
||||||
|
|
||||||
|
public function __construct(protected Connection $connection)
|
||||||
|
{
|
||||||
|
parent::__construct(Event::FUNCTIONS_QUEUE_NAME, Event::FUNCTIONS_CLASS_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets function document for the function event.
|
||||||
|
*
|
||||||
|
* @param string $namespace
|
||||||
|
* @param string $key
|
||||||
|
* @param int $value
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public function addMetric(string $namespace, string $key, int $value): self
|
||||||
|
{
|
||||||
|
$this->metrics[] = [
|
||||||
|
'namespace' => $namespace,
|
||||||
|
'key' => $key,
|
||||||
|
'value' => $value,
|
||||||
|
];
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes the function event and sends it to the functions worker.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function trigger(): string|bool
|
||||||
|
{
|
||||||
|
$client = new Client($this->queue, $this->connection);
|
||||||
|
|
||||||
|
return $client->enqueue([
|
||||||
|
'project' => $this->project,
|
||||||
|
'user' => $this->user,
|
||||||
|
'type' => $this->type,
|
||||||
|
'payload' => $this->payload,
|
||||||
|
'metrics' => $this->metrics,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue