1
0
Fork 0
mirror of synced 2024-05-17 19:22:34 +12:00

Merge branch '1.1.x' into feat-implement-session-length-api

This commit is contained in:
Bradley Schofield 2022-11-14 15:30:38 +00:00
commit 3c71164741
70 changed files with 999 additions and 1448 deletions

4
.env
View file

@ -76,8 +76,8 @@ _APP_MAINTENANCE_RETENTION_CACHE=2592000
_APP_MAINTENANCE_RETENTION_EXECUTION=1209600
_APP_MAINTENANCE_RETENTION_ABUSE=86400
_APP_MAINTENANCE_RETENTION_AUDIT=1209600
_APP_USAGE_TIMESERIES_INTERVAL=2
_APP_USAGE_DATABASE_INTERVAL=15
_APP_USAGE_AGGREGATION_INTERVAL=5
_APP_MAINTENANCE_RETENTION_USAGE_HOURLY=8640000
_APP_USAGE_STATS=enabled
_APP_LOGGING_PROVIDER=
_APP_LOGGING_CONFIG=

View file

@ -5,7 +5,14 @@
## Bugs
- Fix license detection for Flutter and Dart SDKs [#4435](https://github.com/appwrite/appwrite/pull/4435)
- Fix missing realtime event for create function deployment [#4574](https://github.com/appwrite/appwrite/pull/4574)
- Fix missing status, buildStderr and buildStderr from get deployment response [#4611](https://github.com/appwrite/appwrite/pull/4611)
# Features
- Added Auth Duration API to allow users to set the duration of their sessions. [#4618](https://github.com/appwrite/appwrite/pull/4618)
# Version 1.0.4
- Fix project pagination in DB usage collector [#4517](https://github.com/appwrite/appwrite/pull/4517)
# Version 1.0.3
## Bugs

View file

@ -246,13 +246,13 @@ ENV _APP_SERVER=swoole \
_APP_SETUP=self-hosted \
_APP_VERSION=$VERSION \
_APP_USAGE_STATS=enabled \
_APP_USAGE_TIMESERIES_INTERVAL=30 \
_APP_USAGE_DATABASE_INTERVAL=900 \
_APP_USAGE_AGGREGATION_INTERVAL=30 \
# 14 Days = 1209600 s
_APP_MAINTENANCE_RETENTION_EXECUTION=1209600 \
_APP_MAINTENANCE_RETENTION_AUDIT=1209600 \
# 1 Day = 86400 s
_APP_MAINTENANCE_RETENTION_ABUSE=86400 \
_APP_MAINTENANCE_RETENTION_USAGE_HOURLY=8640000 \
_APP_MAINTENANCE_INTERVAL=86400 \
_APP_LOGGING_PROVIDER= \
_APP_LOGGING_CONFIG=

View file

@ -523,6 +523,17 @@ $collections = [
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('region'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 128,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('description'),
'type' => Database::VAR_STRING,
@ -737,6 +748,13 @@ $collections = [
'lengths' => [128],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_team'),
'type' => Database::INDEX_KEY,
'attributes' => ['teamId'],
'lengths' => [Database::LENGTH_KEY],
'orders' => [Database::ORDER_ASC],
],
],
],
@ -2999,6 +3017,17 @@ $collections = [
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('region'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 255,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('value'),
'type' => Database::VAR_INTEGER,

9
app/config/regions.php Normal file
View file

@ -0,0 +1,9 @@
<?php
return [
'default' => [
'name' => 'Default',
'default' => true,
'disabled' => false,
]
];

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

View file

@ -170,8 +170,8 @@ return [
],
[
'name' => '_APP_USAGE_AGGREGATION_INTERVAL',
'description' => 'Deprecated since 1.0.0, use `_APP_USAGE_TIMESERIES_INTERVAL` and `_APP_USAGE_DATABASE_INTERVAL` instead.',
'introduction' => '0.10.0',
'description' => 'Interval value containing the number of seconds that the Appwrite usage process should wait before aggregating stats and syncing it to Database from TimeSeries data. The default value is 30 seconds. Reintroduced in 1.1.0.',
'introduction' => '1.1.0',
'default' => '30',
'required' => false,
'question' => '',
@ -179,7 +179,7 @@ return [
],
[
'name' => '_APP_USAGE_TIMESERIES_INTERVAL',
'description' => 'Interval value containing the number of seconds that the Appwrite usage process should wait before aggregating stats and syncing it to Appwrite Database from Timeseries Database. The default value is 30 seconds.',
'description' => 'Deprecated since 1.1.0 use _APP_USAGE_AGGREGATION_INTERVAL instead.',
'introduction' => '1.0.0',
'default' => '30',
'required' => false,
@ -188,7 +188,7 @@ return [
],
[
'name' => '_APP_USAGE_DATABASE_INTERVAL',
'description' => 'Interval value containing the number of seconds that the Appwrite usage process should wait before aggregating stats from data in Appwrite Database. The default value is 15 minutes.',
'description' => 'Deprecated since 1.1.0 use _APP_USAGE_AGGREGATION_INTERVAL instead.',
'introduction' => '1.0.0',
'default' => '900',
'required' => false,
@ -857,7 +857,16 @@ return [
'required' => false,
'question' => '',
'filter' => ''
]
],
[
'name' => '_APP_MAINTENANCE_RETENTION_USAGE_HOURLY',
'description' => 'The maximum duration (in seconds) upto which to retain hourly usage metrics. The default value is 8640000 seconds (100 days).',
'introduction' => '',
'default' => '8640000',
'required' => false,
'question' => '',
'filter' => ''
],
],
],
];

View file

@ -2467,8 +2467,8 @@ App::get('/v1/databases/usage')
if (App::getEnv('_APP_USAGE_STATS', 'enabled') == 'enabled') {
$periods = [
'24h' => [
'period' => '30m',
'limit' => 48,
'period' => '1h',
'limit' => 24,
],
'7d' => [
'period' => '1d',
@ -2529,7 +2529,7 @@ App::get('/v1/databases/usage')
while ($backfill > 0) {
$last = $limit - $backfill - 1; // array index of last added metric
$diff = match ($period) { // convert period to seconds for unix timestamp math
'30m' => 1800,
'1h' => 3600,
'1d' => 86400,
};
$stats[$metric][] = [
@ -2586,8 +2586,8 @@ App::get('/v1/databases/:databaseId/usage')
if (App::getEnv('_APP_USAGE_STATS', 'enabled') == 'enabled') {
$periods = [
'24h' => [
'period' => '30m',
'limit' => 48,
'period' => '1h',
'limit' => 24,
],
'7d' => [
'period' => '1d',
@ -2643,7 +2643,7 @@ App::get('/v1/databases/:databaseId/usage')
while ($backfill > 0) {
$last = $limit - $backfill - 1; // array index of last added metric
$diff = match ($period) { // convert period to seconds for unix timestamp math
'30m' => 1800,
'1h' => 3600,
'1d' => 86400,
};
$stats[$metric][] = [
@ -2706,8 +2706,8 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/usage')
if (App::getEnv('_APP_USAGE_STATS', 'enabled') == 'enabled') {
$periods = [
'24h' => [
'period' => '30m',
'limit' => 48,
'period' => '1h',
'limit' => 24,
],
'7d' => [
'period' => '1d',
@ -2758,7 +2758,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/usage')
while ($backfill > 0) {
$last = $limit - $backfill - 1; // array index of last added metric
$diff = match ($period) { // convert period to seconds for unix timestamp math
'30m' => 1800,
'1h' => 3600,
'1d' => 86400,
};
$stats[$metric][] = [

View file

@ -237,8 +237,8 @@ App::get('/v1/functions/:functionId/usage')
if (App::getEnv('_APP_USAGE_STATS', 'enabled') == 'enabled') {
$periods = [
'24h' => [
'period' => '30m',
'limit' => 48,
'period' => '1h',
'limit' => 24,
],
'7d' => [
'period' => '1d',
@ -292,7 +292,7 @@ App::get('/v1/functions/:functionId/usage')
while ($backfill > 0) {
$last = $limit - $backfill - 1; // array index of last added metric
$diff = match ($period) { // convert period to seconds for unix timestamp math
'30m' => 1800,
'1h' => 3600,
'1d' => 86400,
};
$stats[$metric][] = [
@ -340,8 +340,8 @@ App::get('/v1/functions/usage')
if (App::getEnv('_APP_USAGE_STATS', 'enabled') == 'enabled') {
$periods = [
'24h' => [
'period' => '30m',
'limit' => 48,
'period' => '1h',
'limit' => 24,
],
'7d' => [
'period' => '1d',
@ -395,7 +395,7 @@ App::get('/v1/functions/usage')
while ($backfill > 0) {
$last = $limit - $backfill - 1; // array index of last added metric
$diff = match ($period) { // convert period to seconds for unix timestamp math
'30m' => 1800,
'1h' => 3600,
'1d' => 86400,
};
$stats[$metric][] = [
@ -832,6 +832,7 @@ App::get('/v1/functions/:functionId/deployments')
$result->setAttribute('status', $build->getAttribute('status', 'processing'));
$result->setAttribute('buildStderr', $build->getAttribute('stderr', ''));
$result->setAttribute('buildStdout', $build->getAttribute('stdout', ''));
$result->setAttribute('buildTime', $build->getAttribute('duration', 0));
}
$response->dynamic(new Document([
@ -873,6 +874,11 @@ App::get('/v1/functions/:functionId/deployments/:deploymentId')
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
}
$build = $dbForProject->getDocument('builds', $deployment->getAttribute('buildId', ''));
$deployment->setAttribute('status', $build->getAttribute('status', 'processing'));
$deployment->setAttribute('buildStderr', $build->getAttribute('stderr', ''));
$deployment->setAttribute('buildStdout', $build->getAttribute('stdout', ''));
$response->dynamic($deployment, Response::MODEL_DEPLOYMENT);
});

View file

@ -59,6 +59,7 @@ App::post('/v1/projects')
->param('projectId', '', new CustomId(), 'Unique Id. Choose your own unique ID or pass the string `ID.unique()` to auto generate it. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('name', null, new Text(128), 'Project name. Max length: 128 chars.')
->param('teamId', '', new UID(), 'Team unique ID.')
->param('region', '', new Whitelist(array_keys(array_filter(Config::getParam('regions'), fn($config) => !$config['disabled']))), 'Project Region.')
->param('description', '', new Text(256), 'Project description. Max length: 256 chars.', true)
->param('logo', '', new Text(1024), 'Project logo.', true)
->param('url', '', new URL(), 'Project URL.', true)
@ -71,7 +72,7 @@ App::post('/v1/projects')
->inject('response')
->inject('dbForConsole')
->inject('dbForProject')
->action(function (string $projectId, string $name, string $teamId, string $description, string $logo, string $url, string $legalName, string $legalCountry, string $legalState, string $legalCity, string $legalAddress, string $legalTaxId, Response $response, Database $dbForConsole, Database $dbForProject) {
->action(function (string $projectId, string $name, string $teamId, string $region, string $description, string $logo, string $url, string $legalName, string $legalCountry, string $legalState, string $legalCity, string $legalAddress, string $legalTaxId, Response $response, Database $dbForConsole, Database $dbForProject) {
$team = $dbForConsole->getDocument('teams', $teamId);
@ -103,6 +104,7 @@ App::post('/v1/projects')
'name' => $name,
'teamInternalId' => $team->getInternalId(),
'teamId' => $team->getId(),
'region' => $region,
'description' => $description,
'logo' => $logo,
'url' => $url,
@ -270,8 +272,8 @@ App::get('/v1/projects/:projectId/usage')
if (App::getEnv('_APP_USAGE_STATS', 'enabled') == 'enabled') {
$periods = [
'24h' => [
'period' => '30m',
'limit' => 48,
'period' => '1h',
'limit' => 24,
],
'7d' => [
'period' => '1d',
@ -294,9 +296,10 @@ App::get('/v1/projects/:projectId/usage')
'project.$all.network.bandwidth',
'project.$all.storage.size',
'users.$all.count.total',
'collections.$all.count.total',
'databases.$all.count.total',
'documents.$all.count.total',
'executions.$all.compute.total',
'buckets.$all.count.total'
];
$stats = [];
@ -326,7 +329,7 @@ App::get('/v1/projects/:projectId/usage')
while ($backfill > 0) {
$last = $limit - $backfill - 1; // array index of last added metric
$diff = match ($period) { // convert period to seconds for unix timestamp math
'30m' => 1800,
'1h' => 3600,
'1d' => 86400,
};
$stats[$metric][] = [
@ -345,9 +348,10 @@ App::get('/v1/projects/:projectId/usage')
'network' => $stats[$metrics[1]] ?? [],
'storage' => $stats[$metrics[2]] ?? [],
'users' => $stats[$metrics[3]] ?? [],
'collections' => $stats[$metrics[4]] ?? [],
'databases' => $stats[$metrics[4]] ?? [],
'documents' => $stats[$metrics[5]] ?? [],
'executions' => $stats[$metrics[6]] ?? [],
'buckets' => $stats[$metrics[7]] ?? [],
]);
}
@ -443,12 +447,13 @@ App::patch('/v1/projects/:projectId/oauth2')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_PROJECT)
->param('projectId', '', new UID(), 'Project unique ID.')
->param('provider', '', new WhiteList(\array_keys(Config::getParam('providers')), true), 'Provider Name', false)
->param('appId', '', new Text(256), 'Provider app ID. Max length: 256 chars.', true)
->param('secret', '', new text(512), 'Provider secret key. Max length: 512 chars.', true)
->param('provider', '', new WhiteList(\array_keys(Config::getParam('providers')), true), 'Provider Name')
->param('appId', null, new Text(256), 'Provider app ID. Max length: 256 chars.', true)
->param('secret', null, new text(512), 'Provider secret key. Max length: 512 chars.', true)
->param('enabled', null, new Boolean(), 'Provider status. Set to \'false\' to disable new session creation.', true)
->inject('response')
->inject('dbForConsole')
->action(function (string $projectId, string $provider, string $appId, string $secret, Response $response, Database $dbForConsole) {
->action(function (string $projectId, string $provider, ?string $appId, ?string $secret, ?bool $enabled, Response $response, Database $dbForConsole) {
$project = $dbForConsole->getDocument('projects', $projectId);
@ -457,8 +462,18 @@ App::patch('/v1/projects/:projectId/oauth2')
}
$providers = $project->getAttribute('authProviders', []);
$providers[$provider . 'Appid'] = $appId;
$providers[$provider . 'Secret'] = $secret;
if ($appId !== null) {
$providers[$provider . 'Appid'] = $appId;
}
if ($secret !== null) {
$providers[$provider . 'Secret'] = $secret;
}
if ($enabled !== null) {
$providers[$provider . 'Enabled'] = $enabled;
}
$project = $dbForConsole->updateDocument('projects', $project->getId(), $project->setAttribute('authProviders', $providers));
@ -593,10 +608,6 @@ App::delete('/v1/projects/:projectId')
->setDocument($project)
;
if (!$dbForConsole->deleteDocument('teams', $project->getAttribute('teamId', null))) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove project team from DB');
}
if (!$dbForConsole->deleteDocument('projects', $projectId)) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove project from DB');
}

View file

@ -1454,8 +1454,8 @@ App::get('/v1/storage/usage')
if (App::getEnv('_APP_USAGE_STATS', 'enabled') === 'enabled') {
$periods = [
'24h' => [
'period' => '30m',
'limit' => 48,
'period' => '1h',
'limit' => 24,
],
'7d' => [
'period' => '1d',
@ -1513,7 +1513,7 @@ App::get('/v1/storage/usage')
while ($backfill > 0) {
$last = $limit - $backfill - 1; // array index of last added metric
$diff = match ($period) { // convert period to seconds for unix timestamp math
'30m' => 1800,
'1h' => 3600,
'1d' => 86400,
};
$stats[$metric][] = [
@ -1571,8 +1571,8 @@ App::get('/v1/storage/:bucketId/usage')
if (App::getEnv('_APP_USAGE_STATS', 'enabled') === 'enabled') {
$periods = [
'24h' => [
'period' => '30m',
'limit' => 48,
'period' => '1h',
'limit' => 24,
],
'7d' => [
'period' => '1d',
@ -1624,7 +1624,7 @@ App::get('/v1/storage/:bucketId/usage')
while ($backfill > 0) {
$last = $limit - $backfill - 1; // array index of last added metric
$diff = match ($period) { // convert period to seconds for unix timestamp math
'30m' => 1800,
'1h' => 3600,
'1d' => 86400,
};
$stats[$metric][] = [

View file

@ -1116,8 +1116,8 @@ App::get('/v1/users/usage')
if (App::getEnv('_APP_USAGE_STATS', 'enabled') == 'enabled') {
$periods = [
'24h' => [
'period' => '30m',
'limit' => 48,
'period' => '1h',
'limit' => 24,
],
'7d' => [
'period' => '1d',
@ -1171,7 +1171,7 @@ App::get('/v1/users/usage')
while ($backfill > 0) {
$last = $limit - $backfill - 1; // array index of last added metric
$diff = match ($period) { // convert period to seconds for unix timestamp math
'30m' => 1800,
'1h' => 3600,
'1d' => 86400,
};
$stats[$metric][] = [

View file

@ -16,6 +16,7 @@ use Utopia\Abuse\Abuse;
use Utopia\Abuse\Adapters\TimeLimit;
use Utopia\Cache\Adapter\Filesystem;
use Utopia\Cache\Cache;
use Utopia\CLI\Console;
use Utopia\Database\Database;
use Utopia\Database\DateTime;
use Utopia\Database\Document;
@ -47,6 +48,47 @@ $parseLabel = function (string $label, array $responsePayload, array $requestPar
return $label;
};
$databaseListener = function (string $event, Document $document, Stats $usage) {
$multiplier = 1;
if ($event === Database::EVENT_DOCUMENT_DELETE) {
$multiplier = -1;
}
$collection = $document->getCollection();
switch ($collection) {
case 'users':
$usage->setParam('users.{scope}.count.total', 1 * $multiplier);
break;
case 'databases':
$usage->setParam('databases.{scope}.count.total', 1 * $multiplier);
break;
case 'buckets':
$usage->setParam('buckets.{scope}.count.total', 1 * $multiplier);
break;
case 'deployments':
$usage->setParam('deployments.{scope}.storage.size', $document->getAttribute('size') * $multiplier);
break;
default:
if (strpos($collection, 'bucket_') === 0) {
$usage
->setParam('bucketId', $document->getAttribute('bucketId'))
->setParam('files.{scope}.storage.size', $document->getAttribute('sizeOriginal') * $multiplier)
->setParam('files.{scope}.count.total', 1 * $multiplier);
} elseif (strpos($collection, 'database_') === 0) {
$usage
->setParam('databaseId', $document->getAttribute('databaseId'));
if (strpos($collection, '_collection_') !== false) {
$usage
->setParam('collectionId', $document->getAttribute('$collectionId'))
->setParam('documents.{scope}.count.total', 1 * $multiplier);
} else {
$usage->setParam('collections.{scope}.count.total', 1 * $multiplier);
}
}
break;
}
};
App::init()
->groups(['api'])
->inject('utopia')
@ -62,7 +104,7 @@ App::init()
->inject('database')
->inject('dbForProject')
->inject('mode')
->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Event $events, Audit $audits, Mail $mails, Stats $usage, Delete $deletes, EventDatabase $database, Database $dbForProject, string $mode) {
->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Event $events, Audit $audits, Mail $mails, Stats $usage, Delete $deletes, EventDatabase $database, Database $dbForProject, string $mode) use ($databaseListener) {
$route = $utopia->match($request);
@ -149,6 +191,7 @@ App::init()
->setUser($user);
$usage
->setParam('projectInternalId', $project->getInternalId())
->setParam('projectId', $project->getId())
->setParam('project.{scope}.network.requests', 1)
->setParam('httpMethod', $request->getMethod())
@ -158,6 +201,10 @@ App::init()
$deletes->setProject($project);
$database->setProject($project);
$dbForProject->on(Database::EVENT_DOCUMENT_CREATE, fn ($event, Document $document) => $databaseListener($event, $document, $usage));
$dbForProject->on(Database::EVENT_DOCUMENT_DELETE, fn ($event, Document $document) => $databaseListener($event, $document, $usage));
$useCache = $route->getLabel('cache', false);
if ($useCache) {
@ -404,7 +451,6 @@ App::shutdown()
if (
App::getEnv('_APP_USAGE_STATS', 'enabled') == 'enabled'
&& $project->getId()
&& $mode !== APP_MODE_ADMIN // TODO: add check to make sure user is admin
&& !empty($route->getLabel('sdk.namespace', null))
) { // Don't calculate console usage on admin mode
$metric = $route->getLabel('usage.metric', '');

View file

@ -28,6 +28,10 @@ App::shutdown()
$header = new View(__DIR__ . '/../../views/console/comps/header.phtml');
$footer = new View(__DIR__ . '/../../views/console/comps/footer.phtml');
$header
->setParam('regions', Config::getParam('regions', []))
;
$footer
->setParam('home', App::getEnv('_APP_HOME', ''))
->setParam('version', App::getEnv('_APP_VERSION', 'UNKNOWN'))

View file

@ -27,7 +27,6 @@ $http = new Server("0.0.0.0", App::getEnv('PORT', 80));
$payloadSize = 6 * (1024 * 1024); // 6MB
$workerNumber = swoole_cpu_num() * intval(App::getEnv('_APP_WORKER_PER_CORE', 6));
$http
->set([
'worker_num' => $workerNumber,

View file

@ -23,12 +23,6 @@ use Ahc\Jwt\JWT;
use Ahc\Jwt\JWTException;
use Appwrite\Extend\Exception;
use Appwrite\Auth\Auth;
use Appwrite\SMS\Adapter\Mock;
use Appwrite\SMS\Adapter\Telesign;
use Appwrite\SMS\Adapter\TextMagic;
use Appwrite\SMS\Adapter\Twilio;
use Appwrite\SMS\Adapter\Msg91;
use Appwrite\SMS\Adapter\Vonage;
use Appwrite\DSN\DSN;
use Appwrite\Event\Audit;
use Appwrite\Event\Database as EventDatabase;
@ -47,6 +41,12 @@ use Utopia\Database\ID;
use Utopia\Logger\Logger;
use Utopia\Config\Config;
use Utopia\Locale\Locale;
use Utopia\Messaging\Adapters\SMS\Mock;
use Utopia\Messaging\Adapters\SMS\Msg91;
use Utopia\Messaging\Adapters\SMS\Telesign;
use Utopia\Messaging\Adapters\SMS\TextMagic;
use Utopia\Messaging\Adapters\SMS\Twilio;
use Utopia\Messaging\Adapters\SMS\Vonage;
use Utopia\Registry\Registry;
use MaxMind\Db\Reader;
use PHPMailer\PHPMailer\PHPMailer;
@ -188,6 +188,7 @@ Config::load('roles', __DIR__ . '/config/roles.php'); // User roles and scopes
Config::load('scopes', __DIR__ . '/config/scopes.php'); // User roles and scopes
Config::load('services', __DIR__ . '/config/services.php'); // List of services
Config::load('variables', __DIR__ . '/config/variables.php'); // List of env variables
Config::load('regions', __DIR__ . '/config/regions.php'); // List of available regions
Config::load('avatar-browsers', __DIR__ . '/config/avatars/browsers.php');
Config::load('avatar-credit-cards', __DIR__ . '/config/avatars/credit-cards.php');
Config::load('avatar-flags', __DIR__ . '/config/avatars/flags.php');

View file

@ -78,12 +78,11 @@ $cli
->trigger();
}
function notifyDeleteUsageStats(int $interval30m, int $interval1d)
function notifyDeleteUsageStats(int $usageStatsRetentionHourly)
{
(new Delete())
->setType(DELETE_TYPE_USAGE)
->setDateTime1d(DateTime::addSeconds(new \DateTime(), -1 * $interval1d))
->setDateTime30m(DateTime::addSeconds(new \DateTime(), -1 * $interval30m))
->setUsageRetentionHourlyDateTime(DateTime::addSeconds(new \DateTime(), -1 * $usageStatsRetentionHourly))
->trigger();
}
@ -144,11 +143,11 @@ $cli
$executionLogsRetention = (int) App::getEnv('_APP_MAINTENANCE_RETENTION_EXECUTION', '1209600');
$auditLogRetention = (int) App::getEnv('_APP_MAINTENANCE_RETENTION_AUDIT', '1209600');
$abuseLogsRetention = (int) App::getEnv('_APP_MAINTENANCE_RETENTION_ABUSE', '86400');
$usageStatsRetention30m = (int) App::getEnv('_APP_MAINTENANCE_RETENTION_USAGE_30M', '129600'); //36 hours
$usageStatsRetention1d = (int) App::getEnv('_APP_MAINTENANCE_RETENTION_USAGE_1D', '8640000'); // 100 days
$usageStatsRetentionHourly = (int) App::getEnv('_APP_MAINTENANCE_RETENTION_USAGE_HOURLY', '8640000'); //100 days
$cacheRetention = (int) App::getEnv('_APP_MAINTENANCE_RETENTION_CACHE', '2592000'); // 30 days
Console::loop(function () use ($interval, $executionLogsRetention, $abuseLogsRetention, $auditLogRetention, $usageStatsRetention30m, $usageStatsRetention1d, $cacheRetention) {
Console::loop(function () use ($interval, $executionLogsRetention, $abuseLogsRetention, $auditLogRetention, $usageStatsRetentionHourly, $cacheRetention) {
$database = getConsoleDB();
$time = DateTime::now();
@ -157,7 +156,7 @@ $cli
notifyDeleteExecutionLogs($executionLogsRetention);
notifyDeleteAbuseLogs($abuseLogsRetention);
notifyDeleteAuditLogs($auditLogRetention);
notifyDeleteUsageStats($usageStatsRetention30m, $usageStatsRetention1d);
notifyDeleteUsageStats($usageStatsRetentionHourly);
notifyDeleteConnections();
notifyDeleteExpiredSessions();
renewCertificates($database);

View file

@ -2,10 +2,6 @@
global $cli, $register;
use Appwrite\Stats\Usage;
use Appwrite\Stats\UsageDB;
use Appwrite\Usage\Calculators\Aggregator;
use Appwrite\Usage\Calculators\Database;
use Appwrite\Usage\Calculators\TimeSeries;
use InfluxDB\Database as InfluxDatabase;
use Utopia\App;
@ -114,63 +110,29 @@ $logError = function (Throwable $error, string $action = 'syncUsageStats') use (
Console::warning($error->getTraceAsString());
};
function aggregateTimeseries(UtopiaDatabase $database, InfluxDatabase $influxDB, callable $logError): void
{
$interval = (int) App::getEnv('_APP_USAGE_TIMESERIES_INTERVAL', '30'); // 30 seconds (by default)
$usage = new TimeSeries($database, $influxDB, $logError);
Console::loop(function () use ($interval, $usage) {
$now = date('d-m-Y H:i:s', time());
Console::info("[{$now}] Aggregating Timeseries Usage data every {$interval} seconds");
$loopStart = microtime(true);
$usage->collect();
$loopTook = microtime(true) - $loopStart;
$now = date('d-m-Y H:i:s', time());
Console::info("[{$now}] Aggregation took {$loopTook} seconds");
}, $interval);
}
function aggregateDatabase(UtopiaDatabase $database, callable $logError): void
{
$interval = (int) App::getEnv('_APP_USAGE_DATABASE_INTERVAL', '900'); // 15 minutes (by default)
$usage = new Database($database, $logError);
$aggregrator = new Aggregator($database, $logError);
Console::loop(function () use ($interval, $usage, $aggregrator) {
$now = date('d-m-Y H:i:s', time());
Console::info("[{$now}] Aggregating database usage every {$interval} seconds.");
$loopStart = microtime(true);
$usage->collect();
$aggregrator->collect();
$loopTook = microtime(true) - $loopStart;
$now = date('d-m-Y H:i:s', time());
Console::info("[{$now}] Aggregation took {$loopTook} seconds");
}, $interval);
}
$cli
->task('usage')
->param('type', 'timeseries', new WhiteList(['timeseries', 'database']))
->desc('Schedules syncing data from influxdb to Appwrite console db')
->action(function (string $type) use ($register, $logError) {
->action(function () use ($register, $logError) {
Console::title('Usage Aggregation V1');
Console::success(APP_NAME . ' usage aggregation process v1 has started');
$database = getDatabase($register, '_console');
$influxDB = getInfluxDB($register);
switch ($type) {
case 'timeseries':
aggregateTimeseries($database, $influxDB, $logError);
break;
case 'database':
aggregateDatabase($database, $logError);
break;
default:
Console::error("Unsupported usage aggregation type");
}
$interval = (int) App::getEnv('_APP_USAGE_AGGREGATION_INTERVAL', '30'); // 30 seconds (by default)
$region = App::getEnv('region', 'default');
$usage = new TimeSeries($region, $database, $influxDB, $logError);
Console::loop(function () use ($interval, $usage) {
$now = date('d-m-Y H:i:s', time());
Console::info("[{$now}] Aggregating Timeseries Usage data every {$interval} seconds");
$loopStart = microtime(true);
$usage->collect();
$loopTook = microtime(true) - $loopStart;
$now = date('d-m-Y H:i:s', time());
Console::info("[{$now}] Aggregation took {$loopTook} seconds");
}, $interval);
});

View file

@ -1,3 +1,6 @@
<?php
$regions = $this->getParam('regions', []);
?>
<header class="clear" data-version>
<a href="/console" class="logo pull-start">
<img src="/images/appwrite.svg" alt="Appwrite Light Logo" class="force-light" loading="lazy" />
@ -215,7 +218,18 @@
name="projectId" />
<label>Name</label>
<input type="text" class="full-width margin-bottom-xl" name="name" required autocomplete="off" maxlength="128" />
<input type="text" class="full-width" name="name" required autocomplete="off" maxlength="128" />
<?php if(count($regions) > 1): ?>
<label>Region</label>
<select name="region" class="margin-bottom-xl">
<?php foreach($regions as $key => $region): ?>
<option <?php echo ($region['default'] ?? false) ? 'selected' : '' ?> <?php echo ($region['disabled'] ?? false ) ? 'disabled' : '' ?> value="<?php echo $key ?>"><?php echo $region['name'] ?></option>
<?php endforeach; ?>
</select>
<?php else: ?>
<input type="hidden" name="region" value="<?php echo array_key_first($regions) ?>" class="margin-bottom-xl" />
<?php endif; ?>
<footer>
<button type="submit">Create</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>

View file

@ -150,6 +150,7 @@ services:
- _APP_MAINTENANCE_RETENTION_CACHE
- _APP_MAINTENANCE_RETENTION_ABUSE
- _APP_MAINTENANCE_RETENTION_AUDIT
- _APP_MAINTENANCE_RETENTION_USAGE_HOURLY
- _APP_SMS_PROVIDER
- _APP_SMS_FROM
@ -549,13 +550,12 @@ services:
- _APP_MAINTENANCE_RETENTION_CACHE
- _APP_MAINTENANCE_RETENTION_ABUSE
- _APP_MAINTENANCE_RETENTION_AUDIT
- _APP_MAINTENANCE_RETENTION_USAGE_HOURLY
appwrite-usage-timeseries:
appwrite-usage:
image: <?php echo $organization; ?>/<?php echo $image; ?>:<?php echo $version."\n"; ?>
entrypoint:
- usage
- --type=timeseries
container_name: appwrite-usage-timeseries
entrypoint: usage
container_name: appwrite-usage
<<: *x-logging
restart: unless-stopped
networks:
@ -573,40 +573,7 @@ services:
- _APP_DB_PASS
- _APP_INFLUXDB_HOST
- _APP_INFLUXDB_PORT
- _APP_USAGE_TIMESERIES_INTERVAL
- _APP_USAGE_DATABASE_INTERVAL
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
appwrite-usage-database:
image: <?php echo $organization; ?>/<?php echo $image; ?>:<?php echo $version."\n"; ?>
entrypoint:
- usage
- --type=database
container_name: appwrite-usage-database
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
depends_on:
- influxdb
- mariadb
environment:
- _APP_ENV
- _APP_OPENSSL_KEY_V1
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_INFLUXDB_HOST
- _APP_INFLUXDB_PORT
- _APP_USAGE_TIMESERIES_INTERVAL
- _APP_USAGE_DATABASE_INTERVAL
- _APP_USAGE_AGGREGATION_INTERVAL
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER

View file

@ -181,6 +181,9 @@ class BuildsV1 extends Worker
$build->setAttribute('stderr', $response['stderr']);
$build->setAttribute('stdout', $response['response']);
/* Also update the deployment buildTime */
$deployment->setAttribute('buildTime', $response['duration']);
Console::success("Build id: $buildId created");
/** Set auto deploy */

View file

@ -105,7 +105,7 @@ class DeletesV1 extends Worker
break;
case DELETE_TYPE_USAGE:
$this->deleteUsageStats($this->args['dateTime1d'], $this->args['dateTime30m']);
$this->deleteUsageStats($this->args['dateTime1d'], $this->args['hourlyUsageRetentionDatetime']);
break;
case DELETE_TYPE_CACHE_BY_RESOURCE:
@ -215,21 +215,15 @@ class DeletesV1 extends Worker
/**
* @param string $datetime1d
* @param string $datetime30m
* @param string $hourlyUsageRetentionDatetime
*/
protected function deleteUsageStats(string $datetime1d, string $datetime30m)
protected function deleteUsageStats(string $hourlyUsageRetentionDatetime)
{
$this->deleteForProjectIds(function (string $projectId) use ($datetime1d, $datetime30m) {
$this->deleteForProjectIds(function (string $projectId) use ($hourlyUsageRetentionDatetime) {
$dbForProject = $this->getProjectDB($projectId);
// Delete Usage stats
$this->deleteByGroup('stats', [
Query::lessThan('time', $datetime1d),
Query::equal('period', ['1d']),
], $dbForProject);
$this->deleteByGroup('stats', [
Query::lessThan('time', $datetime30m),
Query::equal('period', ['30m']),
Query::lessThan('time', $hourlyUsageRetentionDatetime),
Query::equal('period', ['1h']),
], $dbForProject);
});
}

View file

@ -1,16 +1,17 @@
<?php
use Appwrite\SMS\Adapter\Mock;
use Appwrite\SMS\Adapter\Telesign;
use Appwrite\SMS\Adapter\TextMagic;
use Appwrite\SMS\Adapter\Twilio;
use Appwrite\SMS\Adapter\Msg91;
use Appwrite\SMS\Adapter\Vonage;
use Appwrite\DSN\DSN;
use Appwrite\Resque\Worker;
use Appwrite\SMS\Adapter;
use Utopia\App;
use Utopia\CLI\Console;
use Utopia\Messaging\Adapter;
use Utopia\Messaging\Adapters\SMS\Mock;
use Utopia\Messaging\Adapters\SMS\Msg91;
use Utopia\Messaging\Adapters\SMS\Telesign;
use Utopia\Messaging\Adapters\SMS\TextMagic;
use Utopia\Messaging\Adapters\SMS\Twilio;
use Utopia\Messaging\Adapters\SMS\Vonage;
use Utopia\Messaging\Messages\SMS;
require_once __DIR__ . '/../init.php';
@ -58,11 +59,14 @@ class MessagingV1 extends Worker
return;
}
$recipient = $this->args['recipient'];
$message = $this->args['message'];
$message = new SMS(
to: [$this->args['recipient']],
content: $this->args['message'],
from: $this->from,
);
try {
$this->sms->send($this->from, $recipient, $message);
$this->sms->send($message);
} catch (\Exception $error) {
throw new Exception('Error sending message: ' . $error->getMessage(), 500);
}

View file

@ -43,8 +43,6 @@
"ext-sockets": "*",
"appwrite/php-clamav": "1.1.*",
"appwrite/php-runtimes": "0.11.*",
"utopia-php/framework": "0.21.*",
"utopia-php/logger": "0.3.*",
"utopia-php/abuse": "0.16.*",
"utopia-php/analytics": "0.2.*",
"utopia-php/audit": "0.17.*",
@ -52,15 +50,18 @@
"utopia-php/cli": "0.13.*",
"utopia-php/config": "0.2.*",
"utopia-php/database": "0.28.*",
"utopia-php/locale": "0.4.*",
"utopia-php/registry": "0.5.*",
"utopia-php/preloader": "0.2.*",
"utopia-php/domains": "1.1.*",
"utopia-php/swoole": "0.3.*",
"utopia-php/storage": "0.11.*",
"utopia-php/websocket": "0.1.0",
"utopia-php/framework": "0.22.*",
"utopia-php/image": "0.5.*",
"utopia-php/locale": "0.4.*",
"utopia-php/logger": "0.3.*",
"utopia-php/messaging": "0.1.*",
"utopia-php/orchestration": "0.6.*",
"utopia-php/registry": "0.5.*",
"utopia-php/storage": "0.11.*",
"utopia-php/swoole": "0.4.*",
"utopia-php/websocket": "0.1.0",
"resque/php-resque": "1.3.6",
"matomo/device-detector": "6.0.0",
"dragonmantank/cron-expression": "3.3.1",
@ -92,4 +93,4 @@
"php": "8.0"
}
}
}
}

93
composer.lock generated
View file

@ -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": "51f81d435f4b5b7a9a6ea8f81b470353",
"content-hash": "4ac687daa09a38688f27be6959ea42a5",
"packages": [
{
"name": "adhocore/jwt",
@ -1879,24 +1879,24 @@
},
{
"name": "utopia-php/framework",
"version": "0.21.1",
"version": "0.22.1",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/framework.git",
"reference": "c81789b87a917da2daf336738170ebe01f50ea18"
"reference": "9f35d36ed4b8fa1c92962c77ef02b49c2f5919df"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/framework/zipball/c81789b87a917da2daf336738170ebe01f50ea18",
"reference": "c81789b87a917da2daf336738170ebe01f50ea18",
"url": "https://api.github.com/repos/utopia-php/framework/zipball/9f35d36ed4b8fa1c92962c77ef02b49c2f5919df",
"reference": "9f35d36ed4b8fa1c92962c77ef02b49c2f5919df",
"shasum": ""
},
"require": {
"php": ">=8.0.0"
},
"require-dev": {
"phpunit/phpunit": "^9.5.10",
"vimeo/psalm": "4.13.1"
"phpunit/phpunit": "^9.5.25",
"vimeo/psalm": "^4.27.0"
},
"type": "library",
"autoload": {
@ -1922,9 +1922,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/framework/issues",
"source": "https://github.com/utopia-php/framework/tree/0.21.1"
"source": "https://github.com/utopia-php/framework/tree/0.22.1"
},
"time": "2022-09-07T09:56:28+00:00"
"time": "2022-10-07T14:51:40+00:00"
},
{
"name": "utopia-php/image",
@ -2095,6 +2095,60 @@
},
"time": "2022-03-18T10:56:57+00:00"
},
{
"name": "utopia-php/messaging",
"version": "0.1.0",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/messaging.git",
"reference": "501272fad666f06bec8f130076862e7981a73f8c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/messaging/zipball/501272fad666f06bec8f130076862e7981a73f8c",
"reference": "501272fad666f06bec8f130076862e7981a73f8c",
"shasum": ""
},
"require": {
"ext-curl": "*",
"php": ">=8.0.0"
},
"require-dev": {
"phpmailer/phpmailer": "6.6.*",
"phpunit/phpunit": "9.5.*",
"squizlabs/php_codesniffer": "^3.6"
},
"type": "library",
"autoload": {
"psr-4": {
"Utopia\\Messaging\\": "src/Utopia/Messaging"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jake Barnby",
"email": "jake@appwrite.io"
}
],
"description": "A simple, light and advanced PHP messaging library",
"keywords": [
"library",
"messaging",
"php",
"upf",
"utopia",
"utopia-php"
],
"support": {
"issues": "https://github.com/utopia-php/messaging/issues",
"source": "https://github.com/utopia-php/messaging/tree/0.1.0"
},
"time": "2022-09-29T11:22:48+00:00"
},
{
"name": "utopia-php/orchestration",
"version": "0.6.0",
@ -2312,16 +2366,16 @@
},
{
"name": "utopia-php/swoole",
"version": "0.3.3",
"version": "0.4.0",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/swoole.git",
"reference": "8312df69233b5dcd3992de88f131f238002749de"
"reference": "536e1f3e78fc0197e4a8ed81b1bf2636a3bc4538"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/swoole/zipball/8312df69233b5dcd3992de88f131f238002749de",
"reference": "8312df69233b5dcd3992de88f131f238002749de",
"url": "https://api.github.com/repos/utopia-php/swoole/zipball/536e1f3e78fc0197e4a8ed81b1bf2636a3bc4538",
"reference": "536e1f3e78fc0197e4a8ed81b1bf2636a3bc4538",
"shasum": ""
},
"require": {
@ -2362,9 +2416,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/swoole/issues",
"source": "https://github.com/utopia-php/swoole/tree/0.3.3"
"source": "https://github.com/utopia-php/swoole/tree/0.4.0"
},
"time": "2022-01-20T09:58:43+00:00"
"time": "2022-10-08T14:32:43+00:00"
},
{
"name": "utopia-php/system",
@ -5064,14 +5118,7 @@
"time": "2022-09-28T08:42:51+00:00"
}
],
"aliases": [
{
"package": "utopia-php/database",
"version": "0.28.0.0",
"alias": "0.26.99",
"alias_normalized": "0.26.99.0"
}
],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"prefer-stable": false,

View file

@ -171,6 +171,7 @@ services:
- _APP_MAINTENANCE_RETENTION_CACHE
- _APP_MAINTENANCE_RETENTION_ABUSE
- _APP_MAINTENANCE_RETENTION_AUDIT
- _APP_MAINTENANCE_RETENTION_USAGE_HOURLY
- _APP_SMS_PROVIDER
- _APP_SMS_FROM
@ -602,13 +603,12 @@ services:
- _APP_MAINTENANCE_RETENTION_CACHE
- _APP_MAINTENANCE_RETENTION_ABUSE
- _APP_MAINTENANCE_RETENTION_AUDIT
- _APP_MAINTENANCE_RETENTION_USAGE_HOURLY
appwrite-usage-timeseries:
entrypoint:
- usage
- --type=timeseries
appwrite-usage:
entrypoint: usage
<<: *x-logging
container_name: appwrite-usage-timeseries
container_name: appwrite-usage
image: appwrite-dev
networks:
- appwrite
@ -629,43 +629,7 @@ services:
- _APP_DB_PASS
- _APP_INFLUXDB_HOST
- _APP_INFLUXDB_PORT
- _APP_USAGE_TIMESERIES_INTERVAL
- _APP_USAGE_DATABASE_INTERVAL
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
appwrite-usage-database:
entrypoint:
- usage
- --type=database
<<: *x-logging
container_name: appwrite-usage-database
image: appwrite-dev
networks:
- appwrite
volumes:
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
- ./dev:/usr/local/dev
depends_on:
- influxdb
- mariadb
environment:
- _APP_ENV
- _APP_OPENSSL_KEY_V1
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_INFLUXDB_HOST
- _APP_INFLUXDB_PORT
- _APP_USAGE_TIMESERIES_INTERVAL
- _APP_USAGE_DATABASE_INTERVAL
- _APP_USAGE_AGGREGATION_INTERVAL
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER

View file

@ -519,12 +519,14 @@ class Projects extends Service{constructor(client){super(client);}
list(queries,search){return __awaiter(this,void 0,void 0,function*(){let path='/projects';let payload={};if(typeof queries!=='undefined'){payload['queries']=queries;}
if(typeof search!=='undefined'){payload['search']=search;}
const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('get',uri,{'content-type':'application/json',},payload);});}
create(projectId,name,teamId,description,logo,url,legalName,legalCountry,legalState,legalCity,legalAddress,legalTaxId){return __awaiter(this,void 0,void 0,function*(){if(typeof projectId==='undefined'){throw new AppwriteException('Missing required parameter: "projectId"');}
create(projectId,name,teamId,region,description,logo,url,legalName,legalCountry,legalState,legalCity,legalAddress,legalTaxId){return __awaiter(this,void 0,void 0,function*(){if(typeof projectId==='undefined'){throw new AppwriteException('Missing required parameter: "projectId"');}
if(typeof name==='undefined'){throw new AppwriteException('Missing required parameter: "name"');}
if(typeof teamId==='undefined'){throw new AppwriteException('Missing required parameter: "teamId"');}
if(typeof region==='undefined'){throw new AppwriteException('Missing required parameter: "region"');}
let path='/projects';let payload={};if(typeof projectId!=='undefined'){payload['projectId']=projectId;}
if(typeof name!=='undefined'){payload['name']=name;}
if(typeof teamId!=='undefined'){payload['teamId']=teamId;}
if(typeof region!=='undefined'){payload['region']=region;}
if(typeof description!=='undefined'){payload['description']=description;}
if(typeof logo!=='undefined'){payload['logo']=logo;}
if(typeof url!=='undefined'){payload['url']=url;}
@ -537,9 +539,11 @@ if(typeof legalTaxId!=='undefined'){payload['legalTaxId']=legalTaxId;}
const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('post',uri,{'content-type':'application/json',},payload);});}
get(projectId){return __awaiter(this,void 0,void 0,function*(){if(typeof projectId==='undefined'){throw new AppwriteException('Missing required parameter: "projectId"');}
let path='/projects/{projectId}'.replace('{projectId}',projectId);let payload={};const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('get',uri,{'content-type':'application/json',},payload);});}
update(projectId,name,description,logo,url,legalName,legalCountry,legalState,legalCity,legalAddress,legalTaxId){return __awaiter(this,void 0,void 0,function*(){if(typeof projectId==='undefined'){throw new AppwriteException('Missing required parameter: "projectId"');}
update(projectId,name,region,description,logo,url,legalName,legalCountry,legalState,legalCity,legalAddress,legalTaxId){return __awaiter(this,void 0,void 0,function*(){if(typeof projectId==='undefined'){throw new AppwriteException('Missing required parameter: "projectId"');}
if(typeof name==='undefined'){throw new AppwriteException('Missing required parameter: "name"');}
if(typeof region==='undefined'){throw new AppwriteException('Missing required parameter: "region"');}
let path='/projects/{projectId}'.replace('{projectId}',projectId);let payload={};if(typeof name!=='undefined'){payload['name']=name;}
if(typeof region!=='undefined'){payload['region']=region;}
if(typeof description!=='undefined'){payload['description']=description;}
if(typeof logo!=='undefined'){payload['logo']=logo;}
if(typeof url!=='undefined'){payload['url']=url;}
@ -4145,7 +4149,7 @@ element.classList.add('scroll-to-bottom')}
else{element.classList.remove('scroll-to-bottom')
element.classList.add('scroll-to-top')}
position=direction;let current=Math.ceil(direction/window.innerHeight);element.setAttribute('data-views-total',Math.ceil(element.scrollHeight/window.innerHeight));element.setAttribute('data-views-current',current);if(element.scrollHeight<=(direction+element.offsetHeight+300)&&direction>0){element.classList.add('scroll-end')}
else{element.classList.remove('scroll-end')}};window.addEventListener('scroll',check,false);window.addEventListener('resize',check,false);check();}});})(window);(function(window){window.ls.container.get("view").add({selector:"data-setup",controller:function(element,console,form,alerts,router){element.addEventListener("submit",function(event){event.preventDefault();let loaderId=alerts.add({text:'Creating new project...',class:""},0);let formData=form.toJson(element);formData["name"]=formData["name"]||(element.dataset["defaultName"]||"");console.teams.create('unique()',formData["name"]||"").then(function(data){let team=data["$id"];formData=JSON.parse(JSON.stringify(formData).replace(new RegExp("{{teamId}}","g"),team));console.projects.create(formData["projectId"],formData["name"],team).then(function(project){alerts.remove(loaderId);window.location.href="/console/home?project="+project["$id"];},function(){throw new Error("Failed to setup project");});},function(){throw new Error("Setup failed creating project team");});});}});})(window);(function(window){window.ls.container.get("view").add({selector:"data-switch",controller:function(element,router,document){let check=function(c){if(!element.value){return;}
else{element.classList.remove('scroll-end')}};window.addEventListener('scroll',check,false);window.addEventListener('resize',check,false);check();}});})(window);(function(window){window.ls.container.get("view").add({selector:"data-setup",controller:function(element,console,form,alerts,router){element.addEventListener("submit",function(event){event.preventDefault();let loaderId=alerts.add({text:'Creating new project...',class:""},0);let formData=form.toJson(element);formData["name"]=formData["name"]||(element.dataset["defaultName"]||"");console.teams.create('unique()',formData["name"]||"").then(function(data){let team=data["$id"];formData=JSON.parse(JSON.stringify(formData).replace(new RegExp("{{teamId}}","g"),team));console.projects.create(formData["projectId"],formData["name"],team,formData['region']).then(function(project){alerts.remove(loaderId);window.location.href="/console/home?project="+project["$id"];},function(){throw new Error("Failed to setup project");});},function(){throw new Error("Setup failed creating project team");});});}});})(window);(function(window){window.ls.container.get("view").add({selector:"data-switch",controller:function(element,router,document){let check=function(c){if(!element.value){return;}
if(element.value===router.params.project){return;}
return router.change("/console/home?project="+element.value);};element.addEventListener("change",function(){check();});}});})(window);(function(window){window.ls.container.get("view").add({selector:"data-general-theme",controller:function(element,router,document){let toggle=function(c){if(document.body.classList.contains('theme-light')){document.body.classList.remove('theme-light');document.body.classList.add('theme-dark');window.localStorage.setItem('user-theme','theme-dark')}
else{document.body.classList.remove('theme-dark');document.body.classList.add('theme-light');window.localStorage.setItem('user-theme','theme-light')}};element.addEventListener("click",function(){toggle();});}});})(window);(function(window){window.ls.container.get("view").add({selector:"data-version",controller:function(alerts,env,cookie){let cookieName="version-update-"+env.VERSION.replace(/\./g,"_");if(!cookie.get(cookieName)){var xhr=new XMLHttpRequest();xhr.open('GET','https://appwrite.io/version',true);xhr.onload=function(){if(this.readyState==4&&this.status==200){let data=JSON.parse(this.responseText);let text='Appwrite version '+data.version+' is available, check the';if(isNewerVersion(env.VERSION,data.version)){alerts.add({text:text,class:"success",link:"https://github.com/appwrite/appwrite/releases",label:'release notes',callback:function(){cookie.set(cookieName,"true",365*10);}},0);}}};xhr.send(null);function isNewerVersion(oldVer,newVer){const oldParts=oldVer.split('.')

View file

@ -519,12 +519,14 @@ class Projects extends Service{constructor(client){super(client);}
list(queries,search){return __awaiter(this,void 0,void 0,function*(){let path='/projects';let payload={};if(typeof queries!=='undefined'){payload['queries']=queries;}
if(typeof search!=='undefined'){payload['search']=search;}
const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('get',uri,{'content-type':'application/json',},payload);});}
create(projectId,name,teamId,description,logo,url,legalName,legalCountry,legalState,legalCity,legalAddress,legalTaxId){return __awaiter(this,void 0,void 0,function*(){if(typeof projectId==='undefined'){throw new AppwriteException('Missing required parameter: "projectId"');}
create(projectId,name,teamId,region,description,logo,url,legalName,legalCountry,legalState,legalCity,legalAddress,legalTaxId){return __awaiter(this,void 0,void 0,function*(){if(typeof projectId==='undefined'){throw new AppwriteException('Missing required parameter: "projectId"');}
if(typeof name==='undefined'){throw new AppwriteException('Missing required parameter: "name"');}
if(typeof teamId==='undefined'){throw new AppwriteException('Missing required parameter: "teamId"');}
if(typeof region==='undefined'){throw new AppwriteException('Missing required parameter: "region"');}
let path='/projects';let payload={};if(typeof projectId!=='undefined'){payload['projectId']=projectId;}
if(typeof name!=='undefined'){payload['name']=name;}
if(typeof teamId!=='undefined'){payload['teamId']=teamId;}
if(typeof region!=='undefined'){payload['region']=region;}
if(typeof description!=='undefined'){payload['description']=description;}
if(typeof logo!=='undefined'){payload['logo']=logo;}
if(typeof url!=='undefined'){payload['url']=url;}
@ -537,9 +539,11 @@ if(typeof legalTaxId!=='undefined'){payload['legalTaxId']=legalTaxId;}
const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('post',uri,{'content-type':'application/json',},payload);});}
get(projectId){return __awaiter(this,void 0,void 0,function*(){if(typeof projectId==='undefined'){throw new AppwriteException('Missing required parameter: "projectId"');}
let path='/projects/{projectId}'.replace('{projectId}',projectId);let payload={};const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('get',uri,{'content-type':'application/json',},payload);});}
update(projectId,name,description,logo,url,legalName,legalCountry,legalState,legalCity,legalAddress,legalTaxId){return __awaiter(this,void 0,void 0,function*(){if(typeof projectId==='undefined'){throw new AppwriteException('Missing required parameter: "projectId"');}
update(projectId,name,region,description,logo,url,legalName,legalCountry,legalState,legalCity,legalAddress,legalTaxId){return __awaiter(this,void 0,void 0,function*(){if(typeof projectId==='undefined'){throw new AppwriteException('Missing required parameter: "projectId"');}
if(typeof name==='undefined'){throw new AppwriteException('Missing required parameter: "name"');}
if(typeof region==='undefined'){throw new AppwriteException('Missing required parameter: "region"');}
let path='/projects/{projectId}'.replace('{projectId}',projectId);let payload={};if(typeof name!=='undefined'){payload['name']=name;}
if(typeof region!=='undefined'){payload['region']=region;}
if(typeof description!=='undefined'){payload['description']=description;}
if(typeof logo!=='undefined'){payload['logo']=logo;}
if(typeof url!=='undefined'){payload['url']=url;}

View file

@ -739,7 +739,7 @@ element.classList.add('scroll-to-bottom')}
else{element.classList.remove('scroll-to-bottom')
element.classList.add('scroll-to-top')}
position=direction;let current=Math.ceil(direction/window.innerHeight);element.setAttribute('data-views-total',Math.ceil(element.scrollHeight/window.innerHeight));element.setAttribute('data-views-current',current);if(element.scrollHeight<=(direction+element.offsetHeight+300)&&direction>0){element.classList.add('scroll-end')}
else{element.classList.remove('scroll-end')}};window.addEventListener('scroll',check,false);window.addEventListener('resize',check,false);check();}});})(window);(function(window){window.ls.container.get("view").add({selector:"data-setup",controller:function(element,console,form,alerts,router){element.addEventListener("submit",function(event){event.preventDefault();let loaderId=alerts.add({text:'Creating new project...',class:""},0);let formData=form.toJson(element);formData["name"]=formData["name"]||(element.dataset["defaultName"]||"");console.teams.create('unique()',formData["name"]||"").then(function(data){let team=data["$id"];formData=JSON.parse(JSON.stringify(formData).replace(new RegExp("{{teamId}}","g"),team));console.projects.create(formData["projectId"],formData["name"],team).then(function(project){alerts.remove(loaderId);window.location.href="/console/home?project="+project["$id"];},function(){throw new Error("Failed to setup project");});},function(){throw new Error("Setup failed creating project team");});});}});})(window);(function(window){window.ls.container.get("view").add({selector:"data-switch",controller:function(element,router,document){let check=function(c){if(!element.value){return;}
else{element.classList.remove('scroll-end')}};window.addEventListener('scroll',check,false);window.addEventListener('resize',check,false);check();}});})(window);(function(window){window.ls.container.get("view").add({selector:"data-setup",controller:function(element,console,form,alerts,router){element.addEventListener("submit",function(event){event.preventDefault();let loaderId=alerts.add({text:'Creating new project...',class:""},0);let formData=form.toJson(element);formData["name"]=formData["name"]||(element.dataset["defaultName"]||"");console.teams.create('unique()',formData["name"]||"").then(function(data){let team=data["$id"];formData=JSON.parse(JSON.stringify(formData).replace(new RegExp("{{teamId}}","g"),team));console.projects.create(formData["projectId"],formData["name"],team,formData['region']).then(function(project){alerts.remove(loaderId);window.location.href="/console/home?project="+project["$id"];},function(){throw new Error("Failed to setup project");});},function(){throw new Error("Setup failed creating project team");});});}});})(window);(function(window){window.ls.container.get("view").add({selector:"data-switch",controller:function(element,router,document){let check=function(c){if(!element.value){return;}
if(element.value===router.params.project){return;}
return router.change("/console/home?project="+element.value);};element.addEventListener("change",function(){check();});}});})(window);(function(window){window.ls.container.get("view").add({selector:"data-general-theme",controller:function(element,router,document){let toggle=function(c){if(document.body.classList.contains('theme-light')){document.body.classList.remove('theme-light');document.body.classList.add('theme-dark');window.localStorage.setItem('user-theme','theme-dark')}
else{document.body.classList.remove('theme-dark');document.body.classList.add('theme-light');window.localStorage.setItem('user-theme','theme-light')}};element.addEventListener("click",function(){toggle();});}});})(window);(function(window){window.ls.container.get("view").add({selector:"data-version",controller:function(alerts,env,cookie){let cookieName="version-update-"+env.VERSION.replace(/\./g,"_");if(!cookie.get(cookieName)){var xhr=new XMLHttpRequest();xhr.open('GET','https://appwrite.io/version',true);xhr.onload=function(){if(this.readyState==4&&this.status==200){let data=JSON.parse(this.responseText);let text='Appwrite version '+data.version+' is available, check the';if(isNewerVersion(env.VERSION,data.version)){alerts.add({text:text,class:"success",link:"https://github.com/appwrite/appwrite/releases",label:'release notes',callback:function(){cookie.set(cookieName,"true",365*10);}},0);}}};xhr.send(null);function isNewerVersion(oldVer,newVer){const oldParts=oldVer.split('.')

View file

@ -4084,6 +4084,7 @@
* @param {string} projectId
* @param {string} name
* @param {string} teamId
* @param {string} region
* @param {string} description
* @param {string} logo
* @param {string} url
@ -4096,7 +4097,7 @@
* @throws {AppwriteException}
* @returns {Promise}
*/
create(projectId, name, teamId, description, logo, url, legalName, legalCountry, legalState, legalCity, legalAddress, legalTaxId) {
create(projectId, name, teamId, region, description, logo, url, legalName, legalCountry, legalState, legalCity, legalAddress, legalTaxId) {
return __awaiter(this, void 0, void 0, function* () {
if (typeof projectId === 'undefined') {
throw new AppwriteException('Missing required parameter: "projectId"');
@ -4107,6 +4108,9 @@
if (typeof teamId === 'undefined') {
throw new AppwriteException('Missing required parameter: "teamId"');
}
if (typeof region === 'undefined') {
throw new AppwriteException('Missing required parameter: "region"');
}
let path = '/projects';
let payload = {};
if (typeof projectId !== 'undefined') {
@ -4118,6 +4122,9 @@
if (typeof teamId !== 'undefined') {
payload['teamId'] = teamId;
}
if (typeof region !== 'undefined') {
payload['region'] = region;
}
if (typeof description !== 'undefined') {
payload['description'] = description;
}
@ -4178,6 +4185,7 @@
*
* @param {string} projectId
* @param {string} name
* @param {string} region
* @param {string} description
* @param {string} logo
* @param {string} url
@ -4190,7 +4198,7 @@
* @throws {AppwriteException}
* @returns {Promise}
*/
update(projectId, name, description, logo, url, legalName, legalCountry, legalState, legalCity, legalAddress, legalTaxId) {
update(projectId, name, region, description, logo, url, legalName, legalCountry, legalState, legalCity, legalAddress, legalTaxId) {
return __awaiter(this, void 0, void 0, function* () {
if (typeof projectId === 'undefined') {
throw new AppwriteException('Missing required parameter: "projectId"');
@ -4198,11 +4206,17 @@
if (typeof name === 'undefined') {
throw new AppwriteException('Missing required parameter: "name"');
}
if (typeof region === 'undefined') {
throw new AppwriteException('Missing required parameter: "region"');
}
let path = '/projects/{projectId}'.replace('{projectId}', projectId);
let payload = {};
if (typeof name !== 'undefined') {
payload['name'] = name;
}
if (typeof region !== 'undefined') {
payload['region'] = region;
}
if (typeof description !== 'undefined') {
payload['description'] = description;
}

View file

@ -23,7 +23,7 @@
)
); //convert to JSON string
console.projects.create(formData["projectId"], formData["name"], team).then(
console.projects.create(formData["projectId"], formData["name"], team, formData['region']).then(
function(project) {
alerts.remove(loaderId);
//router.change("/console/home?project=" + project["$id"]);

View file

@ -11,8 +11,7 @@ class Delete extends Event
protected ?Document $document = null;
protected ?string $resource = null;
protected ?string $datetime = null;
protected ?string $dateTime30m = null;
protected ?string $dateTime1d = null;
protected ?string $hourlyUsageRetentionDatetime = null;
public function __construct()
@ -56,26 +55,14 @@ class Delete extends Event
}
/**
* Set datetime for 1 day interval.
* Sets datetime for 1h interval.
*
* @param string $datetime
* @return self
*/
public function setDateTime1d(string $datetime): self
public function setUsageRetentionHourlyDateTime(string $datetime): self
{
$this->dateTime1d = $datetime;
return $this;
}
/**
* Sets datetime for 30m interval.
*
* @param string $datetime
* @return self
*/
public function setDateTime30m(string $datetime): self
{
$this->dateTime30m = $datetime;
$this->hourlyUsageRetentionDatetime = $datetime;
return $this;
}
@ -140,8 +127,7 @@ class Delete extends Event
'document' => $this->document,
'resource' => $this->resource,
'datetime' => $this->datetime,
'dateTime1d' => $this->dateTime1d,
'dateTime30m' => $this->dateTime30m,
'hourlyUsageRetentionDatetime' => $this->hourlyUsageRetentionDatetime,
]);
}
}

View file

@ -1,75 +0,0 @@
<?php
namespace Appwrite\SMS;
abstract class Adapter
{
/**
* @var string
*/
protected string $user;
/**
* @var string
*/
protected string $secret;
/**
* @param string $key
*/
public function __construct(string $user, string $secret)
{
$this->user = $user;
$this->secret = $secret;
}
/**
* Send Message to phone.
* @param string $from
* @param string $to
* @param string $message
* @return void
*/
abstract public function send(string $from, string $to, string $message): void;
/**
* @param string $method
* @param string $url
* @param array $headers
* @param string $payload
*
* @return string
*/
protected function request(string $method, string $url, array $headers = [], ?string $payload = null, ?string $userpwd = null): string
{
$ch = \curl_init($url);
\curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
\curl_setopt($ch, CURLOPT_HEADER, 0);
\curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
\curl_setopt($ch, CURLOPT_USERAGENT, 'Appwrite Phone Authentication');
if (!is_null($payload)) {
\curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
}
if (!is_null($userpwd)) {
\curl_setopt($ch, CURLOPT_USERPWD, $userpwd);
}
$headers[] = 'Content-length: ' . \strlen($payload);
\curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
$response = (string) \curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
\curl_close($ch);
if ($code >= 400) {
throw new \Exception($response);
}
return $response;
}
}

View file

@ -1,38 +0,0 @@
<?php
namespace Appwrite\SMS\Adapter;
use Appwrite\SMS\Adapter;
// Mock adapter used to E2E test worker
class Mock extends Adapter
{
/**
* @var string
*/
private string $endpoint = 'http://request-catcher:5000/mock-sms';
/**
* @param string $from
* @param string $to
* @param string $message
* @return void
*/
public function send(string $from, string $to, string $message): void
{
$this->request(
method: 'POST',
url: $this->endpoint,
payload: \json_encode([
'message' => $message,
'from' => $from,
'to' => $to
]),
headers: [
"content-type: application/json",
"x-username: {$this->user}",
"x-key: {$this->secret}",
]
);
}
}

View file

@ -1,46 +0,0 @@
<?php
namespace Appwrite\SMS\Adapter;
use Appwrite\SMS\Adapter;
// Reference Material
// https://docs.msg91.com/p/tf9GTextN/e/Irz7-x1PK/MSG91
class Msg91 extends Adapter
{
/**
* @var string
*/
private string $endpoint = 'https://api.msg91.com/api/v5/flow/';
/**
* For Flow based sending SMS sender ID should not be set in flow
* In environment _APP_SMS_PROVIDER format is 'sms://[senderID]:[authKey]@msg91'.
* _APP_SMS_FROM value is flow ID created in Msg91
* Eg. _APP_SMS_PROVIDER = sms://DINESH:5e1e93cad6fc054d8e759a5b@msg91
* _APP_SMS_FROM = 3968636f704b303135323339
* @param string $from-> utilized from for flow id
* @param string $to
* @param string $message
* @return void
*/
public function send(string $from, string $to, string $message): void
{
$to = ltrim($to, '+');
$this->request(
method: 'POST',
url: $this->endpoint,
payload: json_encode([
'sender' => $this->user,
'otp' => $message,
'flow_id' => $from,
'mobiles' => $to
]),
headers: [
"content-type: application/JSON",
"authkey: {$this->secret}",
]
);
}
}

View file

@ -1,39 +0,0 @@
<?php
namespace Appwrite\SMS\Adapter;
use Appwrite\SMS\Adapter;
// Reference Material
// https://developer.telesign.com/enterprise/docs/sms-api-send-an-sms
class Telesign extends Adapter
{
/**
* @var string
*/
private string $endpoint = 'https://rest-api.telesign.com/v1/messaging';
/**
* @param string $from
* @param string $to
* @param string $message
* @return void
* @throws \Appwrite\Extend\Exception
*/
public function send(string $from, string $to, string $message): void
{
$to = ltrim($to, '+');
$this->request(
method: 'POST',
url: $this->endpoint,
payload: \http_build_query([
'message' => $message,
'message_type' => 'otp',
'phone_number' => $to
]),
userpwd: "{$this->user}:{$this->secret}"
);
}
}

View file

@ -1,42 +0,0 @@
<?php
namespace Appwrite\SMS\Adapter;
use Appwrite\SMS\Adapter;
// Reference Material
// https://www.textmagic.com/docs/api/start/
class TextMagic extends Adapter
{
/**
* @var string
*/
private string $endpoint = 'https://rest.textmagic.com/api/v2';
/**
* @param string $from
* @param string $to
* @param string $message
* @return void
*/
public function send(string $from, string $to, string $message): void
{
$to = ltrim($to, '+');
$from = ltrim($from, '+');
$this->request(
method: 'POST',
url: $this->endpoint . '/messages',
payload: \http_build_query([
'text' => $message,
'from' => $from,
'phones' => $to
]),
headers: [
"X-TM-Username: {$this->user}",
"X-TM-Key: {$this->secret}",
]
);
}
}

View file

@ -1,36 +0,0 @@
<?php
namespace Appwrite\SMS\Adapter;
use Appwrite\SMS\Adapter;
// Reference Material
// https://www.twilio.com/docs/sms/api
class Twilio extends Adapter
{
/**
* @var string
*/
private string $endpoint = 'https://api.twilio.com/2010-04-01';
/**
* @param string $from
* @param string $to
* @param string $message
* @return void
*/
public function send(string $from, string $to, string $message): void
{
$this->request(
method: 'POST',
url: "{$this->endpoint}/Accounts/{$this->user}/Messages.json",
payload: \http_build_query([
'Body' => $message,
'From' => $from,
'To' => $to
]),
userpwd: "{$this->user}:{$this->secret}"
);
}
}

View file

@ -1,41 +0,0 @@
<?php
namespace Appwrite\SMS\Adapter;
use Appwrite\SMS\Adapter;
// Reference Material
// https://developer.vonage.com/api/sms
class Vonage extends Adapter
{
/**
* @var string
*/
private string $endpoint = 'https://rest.nexmo.com/sms/json';
/**
* @param string $from
* @param string $to
* @param string $message
* @return void
*/
public function send(string $from, string $to, string $message): void
{
$to = ltrim($to, '+');
$headers = ['Content-Type: application/x-www-form-urlencoded'];
$this->request(
method: 'POST',
url: $this->endpoint,
headers: $headers,
payload: \http_build_query([
'text' => $message,
'from' => $from,
'to' => $to,
'api_key' => $this->user,
'api_secret' => $this->secret
])
);
}
}

View file

@ -4,5 +4,12 @@ namespace Appwrite\Usage;
abstract class Calculator
{
protected string $region;
public function __construct(string $region)
{
$this->region = $region;
}
abstract public function collect(): void;
}

View file

@ -1,231 +0,0 @@
<?php
namespace Appwrite\Usage\Calculators;
use DateTime;
use Utopia\Database\Database as UtopiaDatabase;
use Utopia\Database\Document;
use Utopia\Database\Query;
class Aggregator extends Database
{
protected function aggregateDatabaseMetrics(string $projectId): void
{
$this->database->setNamespace('_' . $projectId);
$databasesGeneralMetrics = [
'databases.$all.requests.create',
'databases.$all.requests.read',
'databases.$all.requests.update',
'databases.$all.requests.delete',
'collections.$all.requests.create',
'collections.$all.requests.read',
'collections.$all.requests.update',
'collections.$all.requests.delete',
'documents.$all.requests.create',
'documents.$all.requests.read',
'documents.$all.requests.update',
'documents.$all.requests.delete'
];
foreach ($databasesGeneralMetrics as $metric) {
$this->aggregateDailyMetric($projectId, $metric);
$this->aggregateMonthlyMetric($projectId, $metric);
}
$databasesDatabaseMetrics = [
'collections.databaseId.requests.create',
'collections.databaseId.requests.read',
'collections.databaseId.requests.update',
'collections.databaseId.requests.delete',
'documents.databaseId.requests.create',
'documents.databaseId.requests.read',
'documents.databaseId.requests.update',
'documents.databaseId.requests.delete',
];
$this->foreachDocument($projectId, 'databases', [], function (Document $database) use ($databasesDatabaseMetrics, $projectId) {
$databaseId = $database->getId();
foreach ($databasesDatabaseMetrics as $metric) {
$metric = str_replace('databaseId', $databaseId, $metric);
$this->aggregateDailyMetric($projectId, $metric);
$this->aggregateMonthlyMetric($projectId, $metric);
}
$databasesCollectionMetrics = [
'documents.' . $databaseId . '/collectionId.requests.create',
'documents.' . $databaseId . '/collectionId.requests.read',
'documents.' . $databaseId . '/collectionId.requests.update',
'documents.' . $databaseId . '/collectionId.requests.delete',
];
$this->foreachDocument($projectId, 'database_' . $database->getInternalId(), [], function (Document $collection) use ($databasesCollectionMetrics, $projectId) {
$collectionId = $collection->getId();
foreach ($databasesCollectionMetrics as $metric) {
$metric = str_replace('collectionId', $collectionId, $metric);
$this->aggregateDailyMetric($projectId, $metric);
$this->aggregateMonthlyMetric($projectId, $metric);
}
});
});
}
protected function aggregateStorageMetrics(string $projectId): void
{
$this->database->setNamespace('_' . $projectId);
$storageGeneralMetrics = [
'buckets.$all.requests.create',
'buckets.$all.requests.read',
'buckets.$all.requests.update',
'buckets.$all.requests.delete',
'files.$all.requests.create',
'files.$all.requests.read',
'files.$all.requests.update',
'files.$all.requests.delete',
];
foreach ($storageGeneralMetrics as $metric) {
$this->aggregateDailyMetric($projectId, $metric);
$this->aggregateMonthlyMetric($projectId, $metric);
}
$storageBucketMetrics = [
'files.bucketId.requests.create',
'files.bucketId.requests.read',
'files.bucketId.requests.update',
'files.bucketId.requests.delete',
];
$this->foreachDocument($projectId, 'buckets', [], function (Document $bucket) use ($storageBucketMetrics, $projectId) {
$bucketId = $bucket->getId();
foreach ($storageBucketMetrics as $metric) {
$metric = str_replace('bucketId', $bucketId, $metric);
$this->aggregateDailyMetric($projectId, $metric);
$this->aggregateMonthlyMetric($projectId, $metric);
}
});
}
protected function aggregateFunctionMetrics(string $projectId): void
{
$this->database->setNamespace('_' . $projectId);
$functionsGeneralMetrics = [
'project.$all.compute.total',
'project.$all.compute.time',
'executions.$all.compute.total',
'executions.$all.compute.success',
'executions.$all.compute.failure',
'executions.$all.compute.time',
'builds.$all.compute.total',
'builds.$all.compute.success',
'builds.$all.compute.failure',
'builds.$all.compute.time',
];
foreach ($functionsGeneralMetrics as $metric) {
$this->aggregateDailyMetric($projectId, $metric);
$this->aggregateMonthlyMetric($projectId, $metric);
}
$functionMetrics = [
'executions.functionId.compute.total',
'executions.functionId.compute.success',
'executions.functionId.compute.failure',
'executions.functionId.compute.time',
'builds.functionId.compute.total',
'builds.functionId.compute.success',
'builds.functionId.compute.failure',
'builds.functionId.compute.time',
];
$this->foreachDocument($projectId, 'functions', [], function (Document $function) use ($functionMetrics, $projectId) {
$functionId = $function->getId();
foreach ($functionMetrics as $metric) {
$metric = str_replace('functionId', $functionId, $metric);
$this->aggregateDailyMetric($projectId, $metric);
$this->aggregateMonthlyMetric($projectId, $metric);
}
});
}
protected function aggregateUsersMetrics(string $projectId): void
{
$metrics = [
'users.$all.requests.create',
'users.$all.requests.read',
'users.$all.requests.update',
'users.$all.requests.delete',
'sessions.$all.requests.create',
'sessions.$all.requests.delete'
];
foreach ($metrics as $metric) {
$this->aggregateDailyMetric($projectId, $metric);
$this->aggregateMonthlyMetric($projectId, $metric);
}
}
protected function aggregateGeneralMetrics(string $projectId): void
{
$this->aggregateDailyMetric($projectId, 'project.$all.network.requests');
$this->aggregateDailyMetric($projectId, 'project.$all.network.bandwidth');
$this->aggregateDailyMetric($projectId, 'project.$all.network.inbound');
$this->aggregateDailyMetric($projectId, 'project.$all.network.outbound');
$this->aggregateMonthlyMetric($projectId, 'project.$all.network.requests');
$this->aggregateMonthlyMetric($projectId, 'project.$all.network.bandwidth');
$this->aggregateMonthlyMetric($projectId, 'project.$all.network.inbound');
$this->aggregateMonthlyMetric($projectId, 'project.$all.network.outbound');
}
protected function aggregateDailyMetric(string $projectId, string $metric): void
{
$beginOfDay = DateTime::createFromFormat('Y-m-d\TH:i:s.v', \date('Y-m-d\T00:00:00.000'))->format(DateTime::RFC3339);
$endOfDay = DateTime::createFromFormat('Y-m-d\TH:i:s.v', \date('Y-m-d\T23:59:59.999'))->format(DateTime::RFC3339);
$this->database->setNamespace('_' . $projectId);
$value = (int) $this->database->sum('stats', 'value', [
Query::equal('metric', [$metric]),
Query::equal('period', ['30m']),
Query::greaterThanEqual('time', $beginOfDay),
Query::lessThanEqual('time', $endOfDay),
]);
$this->createOrUpdateMetric($projectId, $metric, '1d', $beginOfDay, $value);
}
protected function aggregateMonthlyMetric(string $projectId, string $metric): void
{
$beginOfMonth = DateTime::createFromFormat('Y-m-d\TH:i:s.v', \date('Y-m-01\T00:00:00.000'))->format(DateTime::RFC3339);
$endOfMonth = DateTime::createFromFormat('Y-m-d\TH:i:s.v', \date('Y-m-t\T23:59:59.999'))->format(DateTime::RFC3339);
$this->database->setNamespace('_' . $projectId);
$value = (int) $this->database->sum('stats', 'value', [
Query::equal('metric', [$metric]),
Query::equal('period', ['1d']),
Query::greaterThanEqual('time', $beginOfMonth),
Query::lessThanEqual('time', $endOfMonth),
]);
$this->createOrUpdateMetric($projectId, $metric, '1mo', $beginOfMonth, $value);
}
/**
* Collect Stats
* Collect all database related stats
*
* @return void
*/
public function collect(): void
{
$this->foreachDocument('console', 'projects', [], function (Document $project) {
$projectId = $project->getInternalId();
// Aggregate new metrics from already collected usage metrics
// for lower time period (1day and 1 month metric from 30 minute metrics)
$this->aggregateGeneralMetrics($projectId);
$this->aggregateFunctionMetrics($projectId);
$this->aggregateDatabaseMetrics($projectId);
$this->aggregateStorageMetrics($projectId);
$this->aggregateUsersMetrics($projectId);
});
}
}

View file

@ -1,360 +0,0 @@
<?php
namespace Appwrite\Usage\Calculators;
use Exception;
use Appwrite\Usage\Calculator;
use DateTime;
use Utopia\Database\Database as UtopiaDatabase;
use Utopia\Database\Document;
use Utopia\Database\Exception\Authorization;
use Utopia\Database\Exception\Structure;
use Utopia\Database\Query;
class Database extends Calculator
{
protected array $periods = [
[
'key' => '30m',
'multiplier' => 1800,
],
[
'key' => '1d',
'multiplier' => 86400,
],
];
public function __construct(UtopiaDatabase $database, callable $errorHandler = null)
{
$this->database = $database;
$this->errorHandler = $errorHandler;
}
/**
* Create Per Period Metric
*
* Create given metric for each defined period
*
* @param string $projectId
* @param string $metric
* @param int $value
* @param bool $monthly
* @return void
* @throws Authorization
* @throws Structure
*/
protected function createPerPeriodMetric(string $projectId, string $metric, int $value, bool $monthly = false): void
{
foreach ($this->periods as $options) {
$period = $options['key'];
$date = new \DateTime();
if ($period === '30m') {
$minutes = $date->format('i') >= '30' ? "30" : "00";
$time = $date->format('Y-m-d H:' . $minutes . ':00');
} elseif ($period === '1d') {
$time = $date->format('Y-m-d 00:00:00');
} else {
throw new Exception("Period type not found", 500);
}
$this->createOrUpdateMetric($projectId, $metric, $period, $time, $value);
}
// Required for billing
if ($monthly) {
$time = DateTime::createFromFormat('Y-m-d\TH:i:s.v', \date('Y-m-01\T00:00:00.000'))->format(DateTime::RFC3339);
$this->createOrUpdateMetric($projectId, $metric, '1mo', $time, $value);
}
}
/**
* Create or Update Metric
*
* Create or update each metric in the stats collection for the given project
*
* @param string $projectId
* @param string $metric
* @param string $period
* @param string $time
* @param int $value
*
* @return void
* @throws Authorization
* @throws Structure
*/
protected function createOrUpdateMetric(string $projectId, string $metric, string $period, string $time, int $value): void
{
$id = \md5("{$time}_{$period}_{$metric}");
$this->database->setNamespace('_' . $projectId);
try {
$document = $this->database->getDocument('stats', $id);
if ($document->isEmpty()) {
$this->database->createDocument('stats', new Document([
'$id' => $id,
'period' => $period,
'time' => $time,
'metric' => $metric,
'value' => $value,
'type' => 2, // these are cumulative metrics
]));
} else {
$this->database->updateDocument(
'stats',
$document->getId(),
$document->setAttribute('value', $value)
);
}
} catch (\Exception$e) { // if projects are deleted this might fail
if (is_callable($this->errorHandler)) {
call_user_func($this->errorHandler, $e, "sync_project_{$projectId}_metric_{$metric}");
} else {
throw $e;
}
}
}
/**
* Foreach Document
*
* Call provided callback for each document in the collection
*
* @param string $projectId
* @param string $collection
* @param array $queries
* @param callable $callback
*
* @return void
* @throws Exception
*/
protected function foreachDocument(string $projectId, string $collection, array $queries, callable $callback): void
{
$limit = 50;
$results = [];
$sum = $limit;
$latestDocument = null;
$this->database->setNamespace('_' . $projectId);
while ($sum === $limit) {
try {
$paginationQueries = [Query::limit($limit)];
if ($latestDocument !== null) {
$paginationQueries[] = Query::cursorAfter($latestDocument);
}
$results = $this->database->find($collection, \array_merge($paginationQueries, $queries));
} catch (\Exception $e) {
if (is_callable($this->errorHandler)) {
call_user_func($this->errorHandler, $e, "fetch_documents_project_{$projectId}_collection_{$collection}");
return;
} else {
throw $e;
}
}
if (empty($results)) {
return;
}
$sum = count($results);
foreach ($results as $document) {
if (is_callable($callback)) {
$callback($document);
}
}
$latestDocument = $results[array_key_last($results)];
}
}
/**
* Sum
*
* Calculate sum of an attribute of documents in collection
*
* @param string $projectId
* @param string $collection
* @param string $attribute
* @param string|null $metric
* @param int $multiplier
* @return int
* @throws Exception
*/
private function sum(string $projectId, string $collection, string $attribute, string $metric = null, int $multiplier = 1): int
{
$this->database->setNamespace('_' . $projectId);
try {
$sum = $this->database->sum($collection, $attribute);
$sum = (int) ($sum * $multiplier);
if (!is_null($metric)) {
$this->createPerPeriodMetric($projectId, $metric, $sum);
}
return $sum;
} catch (Exception $e) {
if (is_callable($this->errorHandler)) {
call_user_func($this->errorHandler, $e, "fetch_sum_project_{$projectId}_collection_{$collection}");
} else {
throw $e;
}
}
return 0;
}
/**
* Count
*
* Count number of documents in collection
*
* @param string $projectId
* @param string $collection
* @param ?string $metric
*
* @return int
* @throws Exception
*/
private function count(string $projectId, string $collection, ?string $metric = null): int
{
$this->database->setNamespace('_' . $projectId);
try {
$count = $this->database->count($collection);
if (!is_null($metric)) {
$this->createPerPeriodMetric($projectId, (string) $metric, $count);
}
return $count;
} catch (Exception $e) {
if (is_callable($this->errorHandler)) {
call_user_func($this->errorHandler, $e, "fetch_count_project_{$projectId}_collection_{$collection}");
} else {
throw $e;
}
}
return 0;
}
/**
* Deployments Total
*
* Total sum of storage used by deployments
*
* @param string $projectId
*
* @return int
* @throws Exception
*/
private function deploymentsTotal(string $projectId): int
{
return $this->sum($projectId, 'deployments', 'size', 'deployments.$all.storage.size');
}
/**
* Users Stats
*
* Metric: users.count
*
* @param string $projectId
*
* @return void
* @throws Exception
*/
private function usersStats(string $projectId): void
{
$this->count($projectId, 'users', 'users.$all.count.total');
}
/**
* Storage Stats
*
* Metrics: buckets.$all.count.total, files.$all.count.total, files.bucketId,count.total,
* files.$all.storage.size, files.bucketId.storage.size, project.$all.storage.size
*
* @param string $projectId
*
* @return void
* @throws Authorization
* @throws Structure
*/
private function storageStats(string $projectId): void
{
$projectFilesTotal = 0;
$projectFilesCount = 0;
$metric = 'buckets.$all.count.total';
$this->count($projectId, 'buckets', $metric);
$this->foreachDocument($projectId, 'buckets', [], function ($bucket) use (&$projectFilesCount, &$projectFilesTotal, $projectId,) {
$metric = "files.{$bucket->getId()}.count.total";
$count = $this->count($projectId, 'bucket_' . $bucket->getInternalId(), $metric);
$projectFilesCount += $count;
$metric = "files.{$bucket->getId()}.storage.size";
$sum = $this->sum($projectId, 'bucket_' . $bucket->getInternalId(), 'sizeOriginal', $metric);
$projectFilesTotal += $sum;
});
$this->createPerPeriodMetric($projectId, 'files.$all.count.total', $projectFilesCount);
$this->createPerPeriodMetric($projectId, 'files.$all.storage.size', $projectFilesTotal);
$deploymentsTotal = $this->deploymentsTotal($projectId);
$this->createPerPeriodMetric($projectId, 'project.$all.storage.size', $projectFilesTotal + $deploymentsTotal);
}
/**
* Database Stats
*
* Collect all database stats
* Metrics: databases.$all.count.total, collections.$all.count.total, collections.databaseId.count.total,
* documents.$all.count.all, documents.databaseId.count.total, documents.databaseId/collectionId.count.total
*
* @param string $projectId
*
* @return void
* @throws Authorization
* @throws Structure
*/
private function databaseStats(string $projectId): void
{
$projectDocumentsCount = 0;
$projectCollectionsCount = 0;
$this->count($projectId, 'databases', 'databases.$all.count.total');
$this->foreachDocument($projectId, 'databases', [], function ($database) use (&$projectDocumentsCount, &$projectCollectionsCount, $projectId) {
$metric = "collections.{$database->getId()}.count.total";
$count = $this->count($projectId, 'database_' . $database->getInternalId(), $metric);
$projectCollectionsCount += $count;
$databaseDocumentsCount = 0;
$this->foreachDocument($projectId, 'database_' . $database->getInternalId(), [], function ($collection) use (&$projectDocumentsCount, &$databaseDocumentsCount, $projectId, $database) {
$metric = "documents.{$database->getId()}/{$collection->getId()}.count.total";
$count = $this->count($projectId, 'database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $metric);
$projectDocumentsCount += $count;
$databaseDocumentsCount += $count;
});
$this->createPerPeriodMetric($projectId, "documents.{$database->getId()}.count.total", $databaseDocumentsCount);
});
$this->createPerPeriodMetric($projectId, 'collections.$all.count.total', $projectCollectionsCount);
$this->createPerPeriodMetric($projectId, 'documents.$all.count.total', $projectDocumentsCount);
}
/**
* Collect Stats
*
* Collect all database related stats
*
* @return void
* @throws Exception
*/
public function collect(): void
{
$this->foreachDocument('console', 'projects', [], function (Document $project) {
$projectId = $project->getInternalId();
$this->usersStats($projectId);
$this->databaseStats($projectId);
$this->storageStats($projectId);
});
}
}

View file

@ -2,6 +2,7 @@
namespace Appwrite\Usage\Calculators;
use Utopia\App;
use Appwrite\Usage\Calculator;
use Utopia\Database\Database;
use Utopia\Database\Document;
@ -10,12 +11,54 @@ use DateTime;
class TimeSeries extends Calculator
{
/**
* InfluxDB
*
* @var InfluxDatabase
*/
protected InfluxDatabase $influxDB;
/**
* Utopia Database
*
* @var Database
*/
protected Database $database;
/**
* Error Handler Callback
*
* @var callable
*/
protected $errorHandler;
/**
* Latest times for metric that was synced to the database
*
* @var array
*/
private array $latestTime = [];
// all the mertics that we are collecting
/**
* Periods the metrics are collected for
* @var array
*/
protected array $periods = [
[
'key' => '1h',
'startTime' => '-24 hours'
],
[
'key' => '1d',
'startTime' => '-30 days'
]
];
/**
* All the metrics that we are collecting
*
* @var array
*/
protected array $metrics = [
'project.$all.network.requests' => [
'table' => 'appwrite_usage_project_{scope}_network_requests',
@ -189,12 +232,6 @@ class TimeSeries extends Calculator
'executions.$all.compute.total' => [
'table' => 'appwrite_usage_executions_{scope}_compute',
],
'builds.$all.compute.time' => [
'table' => 'appwrite_usage_executions_{scope}_compute_time',
],
'executions.$all.compute.time' => [
'table' => 'appwrite_usage_executions_{scope}_compute_time',
],
'builds.$all.compute.total' => [
'table' => 'appwrite_usage_builds_{scope}_compute',
],
@ -230,14 +267,7 @@ class TimeSeries extends Calculator
'table' => 'appwrite_usage_builds_{scope}_compute',
'groupBy' => ['functionId'],
],
'executions.functionId.compute.time' => [
'table' => 'appwrite_usage_executions_{scope}_compute_time',
'groupBy' => ['functionId'],
],
'builds.functionId.compute.time' => [
'table' => 'appwrite_usage_builds_{scope}_compute_time',
'groupBy' => ['functionId'],
],
'executions.functionId.compute.failure' => [
'table' => 'appwrite_usage_executions_{scope}_compute',
'groupBy' => ['functionId'],
@ -267,19 +297,94 @@ class TimeSeries extends Calculator
],
],
// counters
'users.$all.count.total' => [
'table' => 'appwrite_usage_users_{scope}_count_total',
],
'buckets.$all.count.total' => [
'table' => 'appwrite_usage_buckets_{scope}_count_total',
],
'files.$all.count.total' => [
'table' => 'appwrite_usage_files_{scope}_count_total',
],
'files.bucketId.count.total' => [
'table' => 'appwrite_usage_files_{scope}_count_total',
'groupBy' => ['bucketId']
],
'databases.$all.count.total' => [
'table' => 'appwrite_usage_databases_{scope}_count_total',
],
'collections.$all.count.total' => [
'table' => 'appwrite_usage_collections_{scope}_count_total',
],
'documents.$all.count.total' => [
'table' => 'appwrite_usage_documents_{scope}_count_total',
],
'collections.databaseId.count.total' => [
'table' => 'appwrite_usage_collections_{scope}_count_total',
'groupBy' => ['databaseId']
],
'documents.databaseId.count.total' => [
'table' => 'appwrite_usage_documents_{scope}_count_total',
'groupBy' => ['databaseId']
],
'documents.databaseId/collectionId.count.total' => [
'table' => 'appwrite_usage_documents_{scope}_count_total',
'groupBy' => ['databaseId', 'collectionId']
],
'deployments.$all.storage.size' => [
'table' => 'appwrite_usage_deployments_{scope}_storage_size',
],
'project.$all.storage.size' => [
'table' => 'appwrite_usage_project_{scope}_storage_size',
],
'files.$all.storage.size' => [
'table' => 'appwrite_usage_files_{scope}_storage_size',
],
'files.$bucketId.storage.size' => [
'table' => 'appwrite_usage_files_{scope}_storage_size',
'groupBy' => ['bucketId']
],
'builds.$all.compute.time' => [
'table' => 'appwrite_usage_executions_{scope}_compute_time',
],
'executions.$all.compute.time' => [
'table' => 'appwrite_usage_executions_{scope}_compute_time',
],
'executions.functionId.compute.time' => [
'table' => 'appwrite_usage_executions_{scope}_compute_time',
'groupBy' => ['functionId'],
],
'builds.functionId.compute.time' => [
'table' => 'appwrite_usage_builds_{scope}_compute_time',
'groupBy' => ['functionId'],
],
'project.$all.compute.time' => [ // Built time + execution time
'table' => 'appwrite_usage_project_{scope}_compute_time',
'groupBy' => ['functionId'],
],
'deployments.$all.storage.size' => [
'table' => 'appwrite_usage_deployments_{scope}_storage_size'
],
'project.$all.storage.size' => [
'table' => 'appwrite_usage_project_{scope}_storage_size'
],
'files.$all.storage.size' => [
'table' => 'appwrite_usage_files_{scope}_storage_size'
],
'files.bucketId.storage.size' => [
'table' => 'appwrite_usage_files_{scope}_storage_size',
'groupBy' => ['bucketId']
]
];
protected array $period = [
'key' => '30m',
'startTime' => '-24 hours',
];
public function __construct(Database $database, InfluxDatabase $influxDB, callable $errorHandler = null)
public function __construct(string $region, Database $database, InfluxDatabase $influxDB, callable $errorHandler = null)
{
parent::__construct($region);
$this->database = $database;
$this->influxDB = $influxDB;
$this->errorHandler = $errorHandler;
@ -301,9 +406,7 @@ class TimeSeries extends Calculator
private function createOrUpdateMetric(string $projectId, string $time, string $period, string $metric, int $value, int $type): void
{
$id = \md5("{$time}_{$period}_{$metric}");
$this->database->setNamespace('_console');
$project = $this->database->getDocument('projects', $projectId);
$this->database->setNamespace('_' . $project->getInternalId());
$this->database->setNamespace('_' . $projectId);
try {
$document = $this->database->getDocument('stats', $id);
@ -315,6 +418,7 @@ class TimeSeries extends Calculator
'metric' => $metric,
'value' => $value,
'type' => $type,
'region' => $this->region,
]));
} else {
$this->database->updateDocument(
@ -365,7 +469,7 @@ class TimeSeries extends Calculator
$query .= "WHERE \"time\" > '{$start}' ";
$query .= "AND \"time\" < '{$end}' ";
$query .= "AND \"metric_type\"='counter' {$filters} ";
$query .= "GROUP BY time({$period['key']}), \"projectId\" {$groupBy} ";
$query .= "GROUP BY time({$period['key']}), \"projectId\", \"projectInternalId\" {$groupBy} ";
$query .= "FILL(null)";
try {
@ -387,9 +491,11 @@ class TimeSeries extends Calculator
}
$value = (!empty($point['value'])) ? $point['value'] : 0;
if (empty($point['projectInternalId'] ?? null)) {
return;
}
$this->createOrUpdateMetric(
$projectId,
$point['projectInternalId'],
$point['time'],
$period['key'],
$metricUpdated,
@ -416,14 +522,16 @@ class TimeSeries extends Calculator
*/
public function collect(): void
{
foreach ($this->metrics as $metric => $options) { //for each metrics
try {
$this->syncFromInfluxDB($metric, $options, $this->period);
} catch (\Exception $e) {
if (is_callable($this->errorHandler)) {
call_user_func($this->errorHandler, $e);
} else {
throw $e;
foreach ($this->periods as $period) {
foreach ($this->metrics as $metric => $options) { //for each metrics
try {
$this->syncFromInfluxDB($metric, $options, $period);
} catch (\Exception $e) {
if (is_callable($this->errorHandler)) {
call_user_func($this->errorHandler, $e);
} else {
throw $e;
}
}
}
}

View file

@ -76,11 +76,14 @@ class Stats
/**
* Submit data to StatsD.
* Send various metrics to StatsD based on the parameters that are set
* @return void
*/
public function submit(): void
{
$projectId = $this->params['projectId'] ?? '';
$tags = ",projectId={$projectId},version=" . App::getEnv('_APP_VERSION', 'UNKNOWN');
$projectInternalId = $this->params['projectInternalId'];
$tags = ",projectInternalId={$projectInternalId},projectId={$projectId},version=" . App::getEnv('_APP_VERSION', 'UNKNOWN');
// the global namespace is prepended to every key (optional)
$this->statsd->setNamespace($this->namespace);
@ -91,8 +94,8 @@ class Stats
$this->statsd->increment('project.{scope}.network.requests' . $tags . ',method=' . \strtolower($httpMethod));
}
$inbound = $this->params['networkRequestSize'] ?? 0;
$outbound = $this->params['networkResponseSize'] ?? 0;
$inbound = $this->params['project.{scope}.network.inbound'] ?? 0;
$outbound = $this->params['project.{scope}.network.outbound'] ?? 0;
$this->statsd->count('project.{scope}.network.inbound' . $tags, $inbound);
$this->statsd->count('project.{scope}.network.outbound' . $tags, $outbound);
$this->statsd->count('project.{scope}.network.bandwidth' . $tags, $inbound + $outbound);
@ -102,12 +105,13 @@ class Stats
'users.{scope}.requests.read',
'users.{scope}.requests.update',
'users.{scope}.requests.delete',
'users.{scope}.count.total',
];
foreach ($usersMetrics as $metric) {
$value = $this->params[$metric] ?? 0;
if ($value >= 1) {
$this->statsd->increment($metric . $tags);
if ($value === 1 || $value === -1) {
$this->statsd->count($metric . $tags, $value);
}
}
@ -124,13 +128,16 @@ class Stats
'documents.{scope}.requests.read',
'documents.{scope}.requests.update',
'documents.{scope}.requests.delete',
'databases.{scope}.count.total',
'collections.{scope}.count.total',
'documents.{scope}.count.total'
];
foreach ($dbMetrics as $metric) {
$value = $this->params[$metric] ?? 0;
if ($value >= 1) {
if ($value === 1 || $value === -1) {
$dbTags = $tags . ",collectionId=" . ($this->params['collectionId'] ?? '') . ",databaseId=" . ($this->params['databaseId'] ?? '');
$this->statsd->increment($metric . $dbTags);
$this->statsd->count($metric . $dbTags, $value);
}
}
@ -143,13 +150,16 @@ class Stats
'files.{scope}.requests.read',
'files.{scope}.requests.update',
'files.{scope}.requests.delete',
'buckets.{scope}.count.total',
'files.{scope}.count.total',
'files.{scope}.storage.size'
];
foreach ($storageMertics as $metric) {
$value = $this->params[$metric] ?? 0;
if ($value >= 1) {
if ($value !== 0) {
$storageTags = $tags . ",bucketId=" . ($this->params['bucketId'] ?? '');
$this->statsd->increment($metric . $storageTags);
$this->statsd->count($metric . $storageTags, $value);
}
}
@ -176,19 +186,30 @@ class Stats
$functionBuildTime = ($this->params['buildTime'] ?? 0) * 1000; // ms
$functionBuildStatus = $this->params['buildStatus'] ?? '';
$functionCompute = $functionExecutionTime + $functionBuildTime;
$functionTags = $tags . ',functionId=' . $functionId;
$deploymentSize = $this->params['deployment.{scope}.storage.size'] ?? 0;
$storageSize = $this->params['files.{scope}.storage.size'] ?? 0;
if ($deploymentSize + $storageSize > 0 || $deploymentSize + $storageSize <= -1) {
$this->statsd->count('project.{scope}.storage.size' . $tags, $deploymentSize + $storageSize);
}
if ($deploymentSize !== 0) {
$this->statsd->count('deployments.{scope}.storage.size' . $functionTags, $deploymentSize);
}
if ($functionExecution >= 1) {
$this->statsd->increment('executions.{scope}.compute' . $tags . ',functionId=' . $functionId . ',functionStatus=' . $functionExecutionStatus);
$this->statsd->increment('executions.{scope}.compute' . $functionTags . ',functionStatus=' . $functionExecutionStatus);
if ($functionExecutionTime > 0) {
$this->statsd->count('executions.{scope}.compute.time' . $tags . ',functionId=' . $functionId, $functionExecutionTime);
$this->statsd->count('executions.{scope}.compute.time' . $functionTags, $functionExecutionTime);
}
}
if ($functionBuild >= 1) {
$this->statsd->increment('builds.{scope}.compute' . $tags . ',functionId=' . $functionId . ',functionBuildStatus=' . $functionBuildStatus);
$this->statsd->count('builds.{scope}.compute.time' . $tags . ',functionId=' . $functionId, $functionBuildTime);
$this->statsd->increment('builds.{scope}.compute' . $functionTags . ',functionBuildStatus=' . $functionBuildStatus);
$this->statsd->count('builds.{scope}.compute.time' . $functionTags, $functionBuildTime);
}
if ($functionBuild + $functionExecution >= 1) {
$this->statsd->count('project.{scope}.compute.time' . $tags . ',functionId=' . $functionId, $functionCompute);
$this->statsd->count('project.{scope}.compute.time' . $functionTags, $functionCompute);
}
$this->reset();

View file

@ -7,7 +7,8 @@ use Appwrite\Utopia\Database\Validator\Queries\Base;
class Projects extends Base
{
public const ALLOWED_ATTRIBUTES = [
'name'
'name',
'teamId'
];
/**

View file

@ -70,6 +70,7 @@ use Appwrite\Utopia\Response\Model\HealthStatus;
use Appwrite\Utopia\Response\Model\HealthTime;
use Appwrite\Utopia\Response\Model\HealthVersion;
use Appwrite\Utopia\Response\Model\Mock; // Keep last
use Appwrite\Utopia\Response\Model\Provider;
use Appwrite\Utopia\Response\Model\Runtime;
use Appwrite\Utopia\Response\Model\UsageBuckets;
use Appwrite\Utopia\Response\Model\UsageCollection;
@ -194,6 +195,8 @@ class Response extends SwooleResponse
public const MODEL_WEBHOOK_LIST = 'webhookList';
public const MODEL_KEY = 'key';
public const MODEL_KEY_LIST = 'keyList';
public const MODEL_PROVIDER = 'provider';
public const MODEL_PROVIDER_LIST = 'providerList';
public const MODEL_PLATFORM = 'platform';
public const MODEL_PLATFORM_LIST = 'platformList';
public const MODEL_DOMAIN = 'domain';
@ -259,6 +262,7 @@ class Response extends SwooleResponse
->setModel(new BaseList('Projects List', self::MODEL_PROJECT_LIST, 'projects', self::MODEL_PROJECT, true, false))
->setModel(new BaseList('Webhooks List', self::MODEL_WEBHOOK_LIST, 'webhooks', self::MODEL_WEBHOOK, true, false))
->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))
@ -312,6 +316,7 @@ class Response extends SwooleResponse
->setModel(new Webhook())
->setModel(new Key())
->setModel(new Domain())
->setModel(new Provider())
->setModel(new Platform())
->setModel(new Variable())
->setModel(new Country())

View file

@ -82,6 +82,12 @@ class Deployment extends Model
'default' => '',
'example' => 'enabled',
])
->addRule('buildTime', [
'type' => self::TYPE_INTEGER,
'description' => 'The current build time in seconds.',
'default' => 0,
'example' => 128,
])
;
}

View file

@ -113,6 +113,13 @@ class Project extends Model
'default' => 0,
'example' => 100,
])
->addRule('providers', [
'type' => Response::MODEL_PROVIDER,
'description' => 'List of Providers.',
'default' => [],
'example' => new \stdClass(),
'array' => true,
])
->addRule('platforms', [
'type' => Response::MODEL_PLATFORM,
'description' => 'List of Platforms.',
@ -144,32 +151,8 @@ class Project extends Model
;
$services = Config::getParam('services', []);
$providers = Config::getParam('providers', []);
$auth = Config::getParam('auth', []);
foreach ($providers as $index => $provider) {
if (!$provider['enabled']) {
continue;
}
$name = (isset($provider['name'])) ? $provider['name'] : 'Unknown';
$this
->addRule('provider' . \ucfirst($index) . 'Appid', [
'type' => self::TYPE_STRING,
'description' => $name . ' OAuth app ID.',
'example' => '123247283472834787438',
'default' => '',
])
->addRule('provider' . \ucfirst($index) . 'Secret', [
'type' => self::TYPE_STRING,
'description' => $name . ' OAuth secret ID.',
'example' => 'djsgudsdsewe43434343dd34...',
'default' => '',
])
;
}
foreach ($auth as $index => $method) {
$name = $method['name'] ?? '';
$key = $method['key'] ?? '';
@ -230,6 +213,7 @@ class Project extends Model
*/
public function filter(Document $document): Document
{
// Services
$values = $document->getAttribute('services', []);
$services = Config::getParam('services', []);
@ -242,6 +226,7 @@ class Project extends Model
$document->setAttribute('serviceStatusFor' . ucfirst($key), $value);
}
// Auth
$authValues = $document->getAttribute('auths', []);
$auth = Config::getParam('auth', []);
@ -254,17 +239,27 @@ class Project extends Model
$document->setAttribute('auth' . ucfirst($key), $value);
}
// Providers
$providers = Config::getParam('providers', []);
$providerValues = $document->getAttribute('authProviders', []);
$projectProviders = [];
foreach ($providers as $key => $provider) {
if (!$provider['enabled']) {
// Disabled by Appwrite configuration, exclude from response
continue;
}
$appId = $providerValues[$key . 'Appid'] ?? '';
$secret = $providerValues[$key . 'Secret'] ?? '';
$document->setAttribute('provider' . ucfirst($key) . 'Appid', $appId)->setAttribute('provider' . ucfirst($key) . 'Secret', $secret);
$projectProviders[] = new Document([
'name' => ucfirst($key),
'appId' => $providerValues[$key . 'Appid'] ?? '',
'secret' => $providerValues[$key . 'Secret'] ?? '',
'enabled' => $providerValues[$key . 'Enabled'] ?? false,
]);
}
$document->setAttribute("providers", $projectProviders);
return $document;
}
}

View file

@ -0,0 +1,63 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model;
class Provider extends Model
{
/**
* @var bool
*/
protected bool $public = false;
public function __construct()
{
$this
->addRule('name', [
'type' => self::TYPE_STRING,
'description' => 'Provider name.',
'default' => '',
'example' => 'GitHub',
])
->addRule('appId', [
'type' => self::TYPE_STRING,
'description' => 'OAuth 2.0 application ID.',
'default' => '',
'example' => '259125845563242502',
])
->addRule('secret', [
'type' => self::TYPE_STRING,
'description' => 'OAuth 2.0 application secret. Might be JSON string if provider requires extra configuration.',
'default' => '',
'example' => 'Bpw_g9c2TGXxfgLshDbSaL8tsCcqgczQ',
])
->addRule('enabled', [
'type' => self::TYPE_BOOLEAN,
'description' => 'Provider is active and can be used to create session.',
'example' => '',
])
;
}
/**
* Get Name
*
* @return string
*/
public function getName(): string
{
return 'Provider';
}
/**
* Get Type
*
* @return string
*/
public function getType(): string
{
return Response::MODEL_PROVIDER;
}
}

View file

@ -17,45 +17,45 @@ class UsageBuckets extends Model
'example' => '30d',
])
->addRule('filesCount', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for total number of files in this bucket.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
->addRule('filesStorage', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for total storage of files in this bucket.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
->addRule('filesCreate', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for files created.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
->addRule('filesRead', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for files read.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
->addRule('filesUpdate', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for files updated.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
->addRule('filesDelete', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for files deleted.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
;

View file

@ -17,38 +17,38 @@ class UsageCollection extends Model
'example' => '30d',
])
->addRule('documentsCount', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for total number of documents.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
->addRule('documentsCreate', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for documents created.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
->addRule('documentsRead', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for documents read.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
->addRule('documentsUpdate', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for documents updated.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
->addRule('documentsDelete', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for documents deleted.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
;

View file

@ -17,73 +17,73 @@ class UsageDatabase extends Model
'example' => '30d',
])
->addRule('documentsCount', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for total number of documents.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
->addRule('collectionsCount', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for total number of collections.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
->addRule('documentsCreate', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for documents created.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
->addRule('documentsRead', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for documents read.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
->addRule('documentsUpdate', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for documents updated.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
->addRule('documentsDelete', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for documents deleted.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
->addRule('collectionsCreate', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for collections created.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
->addRule('collectionsRead', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for collections read.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
->addRule('collectionsUpdate', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for collections updated.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
->addRule('collectionsDelete', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for collections delete.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
;

View file

@ -17,108 +17,108 @@ class UsageDatabases extends Model
'example' => '30d',
])
->addRule('databasesCount', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for total number of documents.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
->addRule('documentsCount', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for total number of documents.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
->addRule('collectionsCount', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for total number of collections.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
->addRule('databasesCreate', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for documents created.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
->addRule('databasesRead', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for documents read.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
->addRule('databasesUpdate', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for documents updated.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
->addRule('databasesDelete', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for total number of collections.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
->addRule('documentsCreate', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for documents created.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
->addRule('documentsRead', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for documents read.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
->addRule('documentsUpdate', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for documents updated.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
->addRule('documentsDelete', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for documents deleted.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
->addRule('collectionsCreate', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for collections created.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
->addRule('collectionsRead', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for collections read.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
->addRule('collectionsUpdate', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for collections updated.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
->addRule('collectionsDelete', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for collections delete.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
;

View file

@ -17,59 +17,59 @@ class UsageFunction extends Model
'example' => '30d',
])
->addRule('executionsTotal', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for number of function executions.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
->addRule('executionsFailure', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for function execution failures.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
->addRule('executionsSuccess', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for function execution successes.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
->addRule('executionsTime', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for function execution duration.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
->addRule('buildsTotal', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for number of function builds.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
->addRule('buildsFailure', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for function build failures.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
->addRule('buildsSuccess', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for function build successes.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
->addRule('buildsTime', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for function build duration.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
;

View file

@ -17,59 +17,59 @@ class UsageFunctions extends Model
'example' => '30d',
])
->addRule('executionsTotal', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for number of function executions.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
->addRule('executionsFailure', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for function execution failures.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
->addRule('executionsSuccess', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for function execution successes.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
->addRule('executionsTime', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for function execution duration.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
->addRule('buildsTotal', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for number of function builds.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
->addRule('buildsFailure', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for function build failures.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
->addRule('buildsSuccess', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for function build successes.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
->addRule('buildsTime', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for function build duration.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
;

View file

@ -17,52 +17,59 @@ class UsageProject extends Model
'example' => '30d',
])
->addRule('requests', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for number of requests.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
->addRule('network', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for consumed bandwidth.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
->addRule('executions', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for function executions.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
->addRule('documents', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for number of documents.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
->addRule('collections', [
'type' => Response::MODEL_METRIC_LIST,
'description' => 'Aggregated stats for number of collections.',
->addRule('databases', [
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for number of databases.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
->addRule('users', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for number of users.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
->addRule('storage', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for the occupied storage size (in bytes).',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
->addRule('buckets', [
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for number of buckets.',
'default' => [],
'example' => [],
'array' => true
])
;

View file

@ -17,80 +17,80 @@ class UsageStorage extends Model
'example' => '30d',
])
->addRule('storage', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for the occupied storage size (in bytes).',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
->addRule('filesCount', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for total number of files.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
->addRule('bucketsCount', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for total number of buckets.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
->addRule('bucketsCreate', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for buckets created.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
->addRule('bucketsRead', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for buckets read.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
->addRule('bucketsUpdate', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for buckets updated.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
->addRule('bucketsDelete', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for buckets deleted.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
->addRule('filesCreate', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for files created.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
->addRule('filesRead', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for files read.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
->addRule('filesUpdate', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for files updated.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
->addRule('filesDelete', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for files deleted.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
;

View file

@ -17,59 +17,59 @@ class UsageUsers extends Model
'example' => '30d',
])
->addRule('usersCount', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for total number of users.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
->addRule('usersCreate', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for users created.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
->addRule('usersRead', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for users read.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
->addRule('usersUpdate', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for users updated.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
->addRule('usersDelete', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for users deleted.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
->addRule('sessionsCreate', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for sessions created.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
->addRule('sessionsProviderCreate', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for sessions created for a provider ( email, anonymous or oauth2 ).',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
->addRule('sessionsDelete', [
'type' => Response::MODEL_METRIC_LIST,
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated stats for sessions deleted.',
'default' => [],
'example' => new \stdClass(),
'example' => [],
'array' => true
])
;

View file

@ -163,7 +163,8 @@ class HTTPTest extends Scope
$response['body'] = json_decode($response['body'], true);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEmpty($response['body']['schemaValidationMessages']);
// looks like recent change in the validator
$this->assertTrue(empty($response['body']['schemaValidationMessages']));
}
}

View file

@ -85,7 +85,7 @@ class UsageTest extends Scope
#[Retry(count: 1)]
public function testUsersStats(array $data): array
{
sleep(35);
sleep(10);
$projectId = $data['projectId'];
$headers = $data['headers'];
@ -102,7 +102,7 @@ class UsageTest extends Scope
$res = $this->client->call(Client::METHOD_GET, '/projects/' . $projectId . '/usage?range=30d', $cheaders);
$res = $res['body'];
$this->assertEquals(8, count($res));
$this->assertEquals(9, count($res));
$this->assertEquals(30, count($res['requests']));
$this->assertEquals(30, count($res['users']));
$this->assertEquals($usersCount, $res['users'][array_key_last($res['users'])]['value']);
@ -114,6 +114,7 @@ class UsageTest extends Scope
'x-appwrite-project' => $projectId,
'x-appwrite-mode' => 'admin'
]));
$requestsCount++;
$res = $res['body'];
$this->assertEquals(10, $res['usersCreate'][array_key_last($res['usersCreate'])]['value']);
$this->validateDates($res['usersCreate']);
@ -255,7 +256,7 @@ class UsageTest extends Scope
$filesCreate = $data['filesCreate'];
$filesDelete = $data['filesDelete'];
sleep(35);
sleep(10);
// console request
$headers = [
@ -267,7 +268,7 @@ class UsageTest extends Scope
$res = $this->client->call(Client::METHOD_GET, '/projects/' . $projectId . '/usage?range=30d', $headers);
$res = $res['body'];
$this->assertEquals(8, count($res));
$this->assertEquals(9, count($res));
$this->assertEquals(30, count($res['requests']));
$this->assertEquals(30, count($res['storage']));
$this->assertEquals($requestsCount, $res['requests'][array_key_last($res['requests'])]['value']);
@ -279,6 +280,7 @@ class UsageTest extends Scope
'x-appwrite-project' => $projectId,
'x-appwrite-mode' => 'admin'
]));
$requestsCount++;
$res = $res['body'];
$this->assertEquals($storageTotal, $res['storage'][array_key_last($res['storage'])]['value']);
$this->validateDates($res['storage']);
@ -303,6 +305,7 @@ class UsageTest extends Scope
'x-appwrite-project' => $projectId,
'x-appwrite-mode' => 'admin'
]));
$requestsCount++;
$res = $res['body'];
$this->assertEquals($storageTotal, $res['filesStorage'][array_key_last($res['filesStorage'])]['value']);
$this->assertEquals($filesCount, $res['filesCount'][array_key_last($res['filesCount'])]['value']);
@ -493,7 +496,7 @@ class UsageTest extends Scope
$documentsRead = $data['documentsRead'];
$documentsDelete = $data['documentsDelete'];
sleep(35);
sleep(10);
// check datbase stats
$headers = [
@ -504,13 +507,13 @@ class UsageTest extends Scope
$res = $this->client->call(Client::METHOD_GET, '/projects/' . $projectId . '/usage?range=30d', $headers);
$res = $res['body'];
$this->assertEquals(8, count($res));
$this->assertEquals(9, count($res));
$this->assertEquals(30, count($res['requests']));
$this->assertEquals(30, count($res['storage']));
$this->assertEquals($requestsCount, $res['requests'][array_key_last($res['requests'])]['value']);
$this->validateDates($res['requests']);
$this->assertEquals($collectionsCount, $res['collections'][array_key_last($res['collections'])]['value']);
$this->validateDates($res['collections']);
$this->assertEquals($databasesCount, $res['databases'][array_key_last($res['databases'])]['value']);
$this->validateDates($res['databases']);
$this->assertEquals($documentsCount, $res['documents'][array_key_last($res['documents'])]['value']);
$this->validateDates($res['documents']);
@ -701,7 +704,7 @@ class UsageTest extends Scope
$executions = $data['executions'];
$failures = $data['failures'];
sleep(25);
sleep(10);
$response = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/usage', $headers, [
'range' => '30d'

View file

@ -42,6 +42,7 @@ trait ProjectCustom
'x-appwrite-project' => 'console',
], [
'projectId' => ID::unique(),
'region' => 'default',
'name' => 'Demo Project',
'teamId' => $team['body']['$id'],
'description' => 'Demo Project Description',

View file

@ -716,12 +716,12 @@ class AccountCustomClientTest extends Scope
$this->assertEquals(400, $response['headers']['status-code']);
\sleep(2);
\sleep(5);
$smsRequest = $this->getLastRequest();
$this->assertEquals('http://request-catcher:5000/mock-sms', $smsRequest['url']);
$this->assertEquals('Appwrite Phone Authentication', $smsRequest['headers']['User-Agent']);
$this->assertEquals('Appwrite Mock Message Sender', $smsRequest['headers']['User-Agent']);
$this->assertEquals('username', $smsRequest['headers']['X-Username']);
$this->assertEquals('password', $smsRequest['headers']['X-Key']);
$this->assertEquals('POST', $smsRequest['method']);

View file

@ -550,6 +550,7 @@ class FunctionsCustomServerTest extends Scope
$this->assertIsArray($function['body']['deployments']);
$this->assertCount(2, $function['body']['deployments']);
$this->assertEquals($function['body']['deployments'][0]['$id'], $data['deploymentId']);
$this->assertGreaterThanOrEqual(2, $function['body']['deployments'][0]['buildTime']);
return $data;
}
@ -568,6 +569,10 @@ class FunctionsCustomServerTest extends Scope
], $this->getHeaders()));
$this->assertEquals(200, $function['headers']['status-code']);
$this->assertEquals(0, $function['body']['buildTime']);
$this->assertNotEmpty($function['body']['status']);
$this->assertNotEmpty($function['body']['buildStdout']);
$this->assertArrayHasKey('buildStderr', $function['body']);
/**
* Test for FAILURE

View file

@ -42,6 +42,7 @@ class ProjectsConsoleClientTest extends Scope
'projectId' => ID::unique(),
'name' => 'Project Test',
'teamId' => $team['body']['$id'],
'region' => 'default',
]);
$this->assertEquals(201, $response['headers']['status-code']);
@ -64,6 +65,7 @@ class ProjectsConsoleClientTest extends Scope
'projectId' => ID::unique(),
'name' => '',
'teamId' => $team['body']['$id'],
'region' => 'default'
]);
$this->assertEquals(400, $response['headers']['status-code']);
@ -74,6 +76,7 @@ class ProjectsConsoleClientTest extends Scope
], $this->getHeaders()), [
'projectId' => ID::unique(),
'name' => 'Project Test',
'region' => 'default'
]);
$this->assertEquals(400, $response['headers']['status-code']);
@ -153,6 +156,7 @@ class ProjectsConsoleClientTest extends Scope
'projectId' => ID::unique(),
'name' => 'Project Test 2',
'teamId' => $team['body']['$id'],
'region' => 'default'
]);
$this->assertEquals(201, $response['headers']['status-code']);
@ -163,6 +167,18 @@ class ProjectsConsoleClientTest extends Scope
$this->assertArrayHasKey('webhooks', $response['body']);
$this->assertArrayHasKey('keys', $response['body']);
$response = $this->client->call(Client::METHOD_GET, '/projects', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [ 'equal("teamId", "' . $team['body']['$id'] . '")' ],
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertNotEmpty($response['body']);
$this->assertCount(1, $response['body']['projects']);
$this->assertEquals($team['body']['$id'], $response['body']['projects'][0]['teamId']);
$response = $this->client->call(Client::METHOD_GET, '/projects', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
@ -319,14 +335,15 @@ class ProjectsConsoleClientTest extends Scope
], $this->getHeaders()));
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals(count($response['body']), 8);
$this->assertEquals(count($response['body']), 9);
$this->assertNotEmpty($response['body']);
$this->assertEquals('30d', $response['body']['range']);
$this->assertIsArray($response['body']['requests']);
$this->assertIsArray($response['body']['network']);
$this->assertIsArray($response['body']['executions']);
$this->assertIsArray($response['body']['documents']);
$this->assertIsArray($response['body']['collections']);
$this->assertIsArray($response['body']['databases']);
$this->assertIsArray($response['body']['buckets']);
$this->assertIsArray($response['body']['users']);
$this->assertIsArray($response['body']['storage']);
@ -529,8 +546,61 @@ class ProjectsConsoleClientTest extends Scope
$this->assertEquals($id, $response['body']['$id']);
foreach ($providers as $key => $provider) {
$this->assertEquals('AppId-' . ucfirst($key), $response['body']['provider' . ucfirst($key) . 'Appid']);
$this->assertEquals('Secret-' . ucfirst($key), $response['body']['provider' . ucfirst($key) . 'Secret']);
$asserted = false;
foreach ($response['body']['providers'] as $responseProvider) {
if ($responseProvider['name'] === ucfirst($key)) {
$this->assertEquals('AppId-' . ucfirst($key), $responseProvider['appId']);
$this->assertEquals('Secret-' . ucfirst($key), $responseProvider['secret']);
$this->assertFalse($responseProvider['enabled']);
$asserted = true;
break;
}
}
$this->assertTrue($asserted);
}
// Enable providers
$i = 0;
foreach ($providers as $key => $provider) {
$response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $id . '/oauth2', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'provider' => $key,
'enabled' => $i === 0 ? false : true // On first provider, test enabled=false
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertNotEmpty($response['body']['$id']);
$i++;
}
$response = $this->client->call(Client::METHOD_GET, '/projects/' . $id, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertNotEmpty($response['body']);
$this->assertEquals($id, $response['body']['$id']);
$i = 0;
foreach ($providers as $key => $provider) {
$asserted = false;
foreach ($response['body']['providers'] as $responseProvider) {
if ($responseProvider['name'] === ucfirst($key)) {
// On first provider, test enabled=false
$this->assertEquals($i !== 0, $responseProvider['enabled']);
$asserted = true;
break;
}
}
$this->assertTrue($asserted);
$i++;
}
/**
@ -786,6 +856,7 @@ class ProjectsConsoleClientTest extends Scope
'projectId' => ID::unique(),
'name' => 'Project Test',
'teamId' => $team['body']['$id'],
'region' => 'default'
]);
$this->assertEquals(201, $project['headers']['status-code']);
@ -2414,4 +2485,83 @@ class ProjectsConsoleClientTest extends Scope
return $data;
}
public function testDeleteProject(): array
{
$data = [];
// Create a team and a project
$team = $this->client->call(Client::METHOD_POST, '/teams', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'teamId' => ID::unique(),
'name' => 'Amating Team',
]);
$this->assertEquals(201, $team['headers']['status-code']);
$this->assertEquals('Amating Team', $team['body']['name']);
$this->assertNotEmpty($team['body']['$id']);
$teamId = $team['body']['$id'];
$project = $this->client->call(Client::METHOD_POST, '/projects', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'projectId' => ID::unique(),
'name' => 'Amazing Project',
'teamId' => $teamId,
'region' => 'default'
]);
$this->assertEquals(201, $project['headers']['status-code']);
$this->assertEquals('Amazing Project', $project['body']['name']);
$this->assertEquals($teamId, $project['body']['teamId']);
$this->assertNotEmpty($project['body']['$id']);
$projectId = $project['body']['$id'];
// Ensure I can get both team and project
$team = $this->client->call(Client::METHOD_GET, '/teams/' . $teamId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$this->assertEquals(200, $team['headers']['status-code']);
$project = $this->client->call(Client::METHOD_GET, '/projects/' . $projectId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$this->assertEquals(200, $project['headers']['status-code']);
// Delete team
$team = $this->client->call(Client::METHOD_DELETE, '/projects/' . $projectId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'password' => 'password'
]);
$this->assertEquals(204, $team['headers']['status-code']);
// Ensure I can get team but not a project
$team = $this->client->call(Client::METHOD_GET, '/teams/' . $teamId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$this->assertEquals(200, $team['headers']['status-code']);
$project = $this->client->call(Client::METHOD_GET, '/projects/' . $projectId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$this->assertEquals(404, $project['headers']['status-code']);
return $data;
}
}

View file

@ -38,10 +38,12 @@ class StatsTest extends TestCase
{
$this->object
->setParam('projectId', 'appwrite_test')
->setParam('projectInternalId', 1)
->setParam('networkRequestSize', 100)
;
$this->assertEquals('appwrite_test', $this->object->getParam('projectId'));
$this->assertEquals(1, $this->object->getParam('projectInternalId'));
$this->assertEquals(100, $this->object->getParam('networkRequestSize'));
$this->object->submit();