mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
chore: composer update
This commit is contained in:
+1
-1
@@ -58,7 +58,7 @@
|
||||
"utopia-php/image": "0.7.*",
|
||||
"utopia-php/locale": "0.4.*",
|
||||
"utopia-php/logger": "0.6.*",
|
||||
"utopia-php/messaging": "0.13.*",
|
||||
"utopia-php/messaging": "0.14.*",
|
||||
"utopia-php/migration": "0.6.*",
|
||||
"utopia-php/orchestration": "0.9.*",
|
||||
"utopia-php/platform": "0.7.1",
|
||||
|
||||
Generated
+7
-7
@@ -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": "f56baa0fb5b78799aa64cfdbb8d92d44",
|
||||
"content-hash": "84ab61b29ade142659ec0de1e098d752",
|
||||
"packages": [
|
||||
{
|
||||
"name": "adhocore/jwt",
|
||||
@@ -3878,16 +3878,16 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/messaging",
|
||||
"version": "0.13.0",
|
||||
"version": "0.14.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/messaging.git",
|
||||
"reference": "0e3e57351fe4fe875ef3ab9a01a7fff5f022de90"
|
||||
"reference": "4ba356a3aa382802727f7e13e0f0152bcc1fc535"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/messaging/zipball/0e3e57351fe4fe875ef3ab9a01a7fff5f022de90",
|
||||
"reference": "0e3e57351fe4fe875ef3ab9a01a7fff5f022de90",
|
||||
"url": "https://api.github.com/repos/utopia-php/messaging/zipball/4ba356a3aa382802727f7e13e0f0152bcc1fc535",
|
||||
"reference": "4ba356a3aa382802727f7e13e0f0152bcc1fc535",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -3923,9 +3923,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/messaging/issues",
|
||||
"source": "https://github.com/utopia-php/messaging/tree/0.13.0"
|
||||
"source": "https://github.com/utopia-php/messaging/tree/0.14.1"
|
||||
},
|
||||
"time": "2024-12-05T08:36:07+00:00"
|
||||
"time": "2025-01-28T06:14:28+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/migration",
|
||||
|
||||
@@ -22,6 +22,8 @@ use Utopia\Messaging\Adapter\Push\APNS;
|
||||
use Utopia\Messaging\Adapter\Push as PushAdapter;
|
||||
use Utopia\Messaging\Adapter\Push\FCM;
|
||||
use Utopia\Messaging\Adapter\SMS as SMSAdapter;
|
||||
use Utopia\Messaging\Adapter\SMS\Fast2SMS;
|
||||
use Utopia\Messaging\Adapter\SMS\GEOSMS;
|
||||
use Utopia\Messaging\Adapter\SMS\Mock;
|
||||
use Utopia\Messaging\Adapter\SMS\Msg91;
|
||||
use Utopia\Messaging\Adapter\SMS\Telesign;
|
||||
@@ -46,6 +48,8 @@ class Messaging extends Action
|
||||
{
|
||||
private ?Local $localDevice = null;
|
||||
|
||||
private ?SMSAdapter $adapter = null;
|
||||
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'messaging';
|
||||
@@ -56,6 +60,9 @@ class Messaging extends Action
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
|
||||
$this->adapter = $this->createInternalSMSAdapter();
|
||||
|
||||
$this
|
||||
->desc('Messaging worker')
|
||||
->inject('message')
|
||||
@@ -381,100 +388,38 @@ class Messaging extends Action
|
||||
|
||||
private function sendInternalSMSMessage(Document $message, Document $project, array $recipients, Log $log): void
|
||||
{
|
||||
if (empty(System::getEnv('_APP_SMS_PROVIDER')) || empty(System::getEnv('_APP_SMS_FROM'))) {
|
||||
throw new \Exception('Skipped SMS processing. Missing "_APP_SMS_PROVIDER" or "_APP_SMS_FROM" environment variables.');
|
||||
if ($this->adapter === null) {
|
||||
Console::warning('Skipped SMS processing. SMS adapter is not set.');
|
||||
return;
|
||||
}
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new \Exception('Project not set in payload');
|
||||
}
|
||||
|
||||
Console::log('Project: ' . $project->getId());
|
||||
|
||||
Console::log('Processing project: ' . $project->getId());
|
||||
$denyList = System::getEnv('_APP_SMS_PROJECTS_DENY_LIST', '');
|
||||
$denyList = explode(',', $denyList);
|
||||
|
||||
if (\in_array($project->getId(), $denyList)) {
|
||||
Console::error('Project is in the deny list. Skipping...');
|
||||
return;
|
||||
}
|
||||
|
||||
$smsDSN = new DSN(System::getEnv('_APP_SMS_PROVIDER'));
|
||||
$host = $smsDSN->getHost();
|
||||
$password = $smsDSN->getPassword();
|
||||
$user = $smsDSN->getUser();
|
||||
|
||||
$log->addTag('type', $host);
|
||||
|
||||
$from = System::getEnv('_APP_SMS_FROM');
|
||||
|
||||
$provider = new Document([
|
||||
'$id' => ID::unique(),
|
||||
'provider' => $host,
|
||||
'type' => MESSAGE_TYPE_SMS,
|
||||
'name' => 'Internal SMS',
|
||||
'enabled' => true,
|
||||
'credentials' => match ($host) {
|
||||
'twilio' => [
|
||||
'accountSid' => $user,
|
||||
'authToken' => $password,
|
||||
// Twilio Messaging Service SIDs always start with MG
|
||||
// https://www.twilio.com/docs/messaging/services
|
||||
'messagingServiceSid' => \str_starts_with($from, 'MG') ? $from : null
|
||||
],
|
||||
'textmagic' => [
|
||||
'username' => $user,
|
||||
'apiKey' => $password
|
||||
],
|
||||
'telesign' => [
|
||||
'customerId' => $user,
|
||||
'apiKey' => $password
|
||||
],
|
||||
'msg91' => [
|
||||
'senderId' => $user,
|
||||
'authKey' => $password,
|
||||
'templateId' => $smsDSN->getParam('templateId', $from),
|
||||
],
|
||||
'vonage' => [
|
||||
'apiKey' => $user,
|
||||
'apiSecret' => $password
|
||||
],
|
||||
default => null
|
||||
},
|
||||
'options' => match ($host) {
|
||||
'twilio' => [
|
||||
'from' => \str_starts_with($from, 'MG') ? null : $from
|
||||
],
|
||||
default => [
|
||||
'from' => $from
|
||||
]
|
||||
}
|
||||
]);
|
||||
|
||||
$adapter = $this->getSmsAdapter($provider);
|
||||
|
||||
$batches = \array_chunk(
|
||||
$from = System::getEnv('_APP_SMS_FROM', '');
|
||||
$sms = new SMS(
|
||||
$recipients,
|
||||
$adapter->getMaxMessagesPerRequest()
|
||||
$message->getAttribute('data')['content'],
|
||||
$from
|
||||
);
|
||||
|
||||
batch(\array_map(function ($batch) use ($message, $provider, $adapter) {
|
||||
return function () use ($batch, $message, $provider, $adapter) {
|
||||
$message->setAttribute('to', $batch);
|
||||
|
||||
$data = $this->buildSmsMessage($message, $provider);
|
||||
|
||||
try {
|
||||
$adapter->send($data);
|
||||
} catch (\Throwable $th) {
|
||||
throw new \Exception('Failed sending to targets with error: ' . $th->getMessage());
|
||||
}
|
||||
};
|
||||
}, $batches));
|
||||
try {
|
||||
$result = $this->adapter->send($sms);
|
||||
} catch (\Throwable $th) {
|
||||
throw new \Exception('Failed sending to targets with error: ' . $th->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private function getSmsAdapter(Document $provider): ?SMSAdapter
|
||||
{
|
||||
$credentials = $provider->getAttribute('credentials');
|
||||
@@ -504,6 +449,12 @@ class Messaging extends Action
|
||||
$credentials['apiKey'] ?? '',
|
||||
$credentials['apiSecret'] ?? ''
|
||||
),
|
||||
'fast2sms' => new Fast2SMS(
|
||||
$credentials['apiKey'] ?? '',
|
||||
$credentials['senderId'] ?? '',
|
||||
$credentials['messageId'] ?? '',
|
||||
$credentials['useDLT'] ?? true
|
||||
),
|
||||
default => null
|
||||
};
|
||||
}
|
||||
@@ -720,4 +671,127 @@ class Messaging extends Action
|
||||
|
||||
return $this->localDevice;
|
||||
}
|
||||
|
||||
private function createInternalSMSAdapter(): ?SMSAdapter
|
||||
{
|
||||
if (empty(System::getEnv('_APP_SMS_PROVIDER')) || empty(System::getEnv('_APP_SMS_FROM'))) {
|
||||
Console::warning('Skipped SMS processing. Missing "_APP_SMS_PROVIDER" or "_APP_SMS_FROM" environment variables.');
|
||||
return null;
|
||||
}
|
||||
|
||||
$providers = System::getEnv('_APP_SMS_PROVIDER', '');
|
||||
|
||||
$dsns = [];
|
||||
if (!empty($providers)) {
|
||||
$providers = explode(',', $providers);
|
||||
foreach ($providers as $provider) {
|
||||
$dsns[] = new DSN($provider);
|
||||
}
|
||||
}
|
||||
|
||||
if (count($dsns) === 1) {
|
||||
$provider = $this->createProviderFromDSN($dsns[0]);
|
||||
$adapter = $this->getSmsAdapter($provider);
|
||||
return $adapter;
|
||||
}
|
||||
|
||||
$defaultDSN = null;
|
||||
$localDSNs = [];
|
||||
|
||||
/** @var DSN $dsn */
|
||||
foreach ($dsns as $dsn) {
|
||||
if ($dsn->getParam('local', '') === 'default') {
|
||||
$defaultDSN = $dsn;
|
||||
} else {
|
||||
$localDSNs[] = $dsn;
|
||||
}
|
||||
}
|
||||
|
||||
if ($defaultDSN === null) {
|
||||
throw new \Exception('No default SMS provider found');
|
||||
}
|
||||
|
||||
$defaultProvider = $this->createProviderFromDSN($defaultDSN);
|
||||
$adapter = $this->getSmsAdapter($defaultProvider);
|
||||
$geosms = new GEOSMS($adapter);
|
||||
|
||||
/** @var DSN $localDSN */
|
||||
foreach ($localDSNs as $localDSN) {
|
||||
try {
|
||||
$provider = $this->createProviderFromDSN($localDSN);
|
||||
$adapter = $this->getSmsAdapter($provider);
|
||||
} catch (\Exception) {
|
||||
Console::warning('Unable to create adapter: ' . $localDSN->getHost());
|
||||
continue;
|
||||
}
|
||||
|
||||
$callingCode = $localDSN->getParam('local', '');
|
||||
if (empty($callingCode)) {
|
||||
Console::warning('Unable to register adapter: ' . $localDSN->getHost() . '. Missing `local` parameter.');
|
||||
continue;
|
||||
}
|
||||
|
||||
$geosms->setLocal($callingCode, $adapter);
|
||||
}
|
||||
return $geosms;
|
||||
}
|
||||
|
||||
private function createProviderFromDSN(DSN $dsn): Document
|
||||
{
|
||||
$host = $dsn->getHost();
|
||||
$password = $dsn->getPassword();
|
||||
$user = $dsn->getUser();
|
||||
$from = System::getEnv('_APP_SMS_FROM');
|
||||
|
||||
$provider = new Document([
|
||||
'$id' => ID::unique(),
|
||||
'provider' => $host,
|
||||
'type' => MESSAGE_TYPE_SMS,
|
||||
'name' => 'Internal SMS',
|
||||
'enabled' => true,
|
||||
'credentials' => match ($host) {
|
||||
'twilio' => [
|
||||
'accountSid' => $user,
|
||||
'authToken' => $password,
|
||||
// Twilio Messaging Service SIDs always start with MG
|
||||
// https://www.twilio.com/docs/messaging/services
|
||||
'messagingServiceSid' => \str_starts_with($from, 'MG') ? $from : null
|
||||
],
|
||||
'textmagic' => [
|
||||
'username' => $user,
|
||||
'apiKey' => $password
|
||||
],
|
||||
'telesign' => [
|
||||
'customerId' => $user,
|
||||
'apiKey' => $password
|
||||
],
|
||||
'msg91' => [
|
||||
'senderId' => $user,
|
||||
'authKey' => $password,
|
||||
'templateId' => $dsn->getParam('templateId', $from),
|
||||
],
|
||||
'vonage' => [
|
||||
'apiKey' => $user,
|
||||
'apiSecret' => $password
|
||||
],
|
||||
'fast2sms' => [
|
||||
'senderId' => $user,
|
||||
'apiKey' => $password,
|
||||
'messageId' => $dsn->getParam('messageId'),
|
||||
'useDLT' => $dsn->getParam('useDLT'),
|
||||
],
|
||||
default => null
|
||||
},
|
||||
'options' => match ($host) {
|
||||
'twilio' => [
|
||||
'from' => \str_starts_with($from, 'MG') ? null : $from
|
||||
],
|
||||
default => [
|
||||
'from' => $from
|
||||
]
|
||||
}
|
||||
]);
|
||||
|
||||
return $provider;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,13 +30,16 @@ class Usage extends Action
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
|
||||
$this
|
||||
->desc('Usage worker')
|
||||
->inject('message')
|
||||
->inject('project')
|
||||
->inject('getProjectDB')
|
||||
->inject('queueForUsageDump')
|
||||
->callback([$this, 'action']);
|
||||
->desc('Usage worker')
|
||||
->inject('message')
|
||||
->inject('project')
|
||||
->inject('getProjectDB')
|
||||
->inject('queueForUsageDump')
|
||||
->callback(function (Message $message, Document $project, callable $getProjectDB, UsageDump $queueForUsageDump) {
|
||||
$this->action($message, $project, $getProjectDB, $queueForUsageDump);
|
||||
});
|
||||
|
||||
$this->aggregationInterval = (int) System::getEnv('_APP_USAGE_AGGREGATION_INTERVAL', '20');
|
||||
$this->lastTriggeredTime = time();
|
||||
@@ -58,6 +61,7 @@ class Usage extends Action
|
||||
throw new Exception('Missing payload');
|
||||
}
|
||||
|
||||
|
||||
if (empty($project->getAttribute('database'))) {
|
||||
var_dump($payload);
|
||||
return;
|
||||
|
||||
@@ -7,7 +7,7 @@ use Utopia\CLI\Console;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\NotFound;
|
||||
use Utopia\Database\Exception\Duplicate;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Queue\Message;
|
||||
use Utopia\System\System;
|
||||
@@ -38,7 +38,9 @@ class UsageDump extends Action
|
||||
$this
|
||||
->inject('message')
|
||||
->inject('getProjectDB')
|
||||
->callback([$this, 'action']);
|
||||
->callback(function (Message $message, callable $getProjectDB) {
|
||||
$this->action($message, $getProjectDB);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -55,247 +57,230 @@ class UsageDump extends Action
|
||||
throw new Exception('Missing payload');
|
||||
}
|
||||
|
||||
try {
|
||||
foreach ($payload['stats'] ?? [] as $stats) {
|
||||
$project = new Document($stats['project'] ?? []);
|
||||
$numberOfKeys = !empty($stats['keys']) ? \count($stats['keys']) : 0;
|
||||
$receivedAt = $stats['receivedAt'] ?? 'NONE';
|
||||
if ($numberOfKeys === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($payload['stats'] ?? [] as $stats) {
|
||||
$project = new Document($stats['project'] ?? []);
|
||||
|
||||
/**
|
||||
* End temp bug fallback
|
||||
*/
|
||||
$numberOfKeys = !empty($stats['keys']) ? count($stats['keys']) : 0;
|
||||
$receivedAt = $stats['receivedAt'] ?? 'NONE';
|
||||
if ($numberOfKeys === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
console::log('['.DateTime::now().'] Id: '.$project->getId(). ' InternalId: '.$project->getInternalId(). ' Db: '.$project->getAttribute('database').' ReceivedAt: '.$receivedAt. ' Keys: '.$numberOfKeys);
|
||||
|
||||
try {
|
||||
$dbForProject = $getProjectDB($project);
|
||||
$projectDocuments = [];
|
||||
$databaseCache = [];
|
||||
$collectionSizeCache = [];
|
||||
|
||||
Console::log('['.DateTime::now().'] Id: '.$project->getId(). ' InternalId: '.$project->getInternalId(). ' Db: '.$project->getAttribute('database').' ReceivedAt: '.$receivedAt. ' Keys: '.$numberOfKeys . ' Started');
|
||||
$start = \microtime(true);
|
||||
|
||||
foreach ($stats['keys'] ?? [] as $key => $value) {
|
||||
if ($value == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (str_contains($key, METRIC_DATABASES_STORAGE)) {
|
||||
try {
|
||||
$this->handleDatabaseStorage($key, $dbForProject);
|
||||
} catch (\Exception $e) {
|
||||
console::error('[' . DateTime::now() . '] failed to calculate database storage for key [' . $key . '] ' . $e->getMessage());
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($this->periods as $period => $format) {
|
||||
$time = 'inf' === $period ? null : \date($format, \time());
|
||||
$time = 'inf' === $period ? null : date($format, time());
|
||||
$id = \md5("{$time}_{$period}_{$key}");
|
||||
|
||||
if (\str_contains($key, METRIC_DATABASES_STORAGE)) {
|
||||
$this->handleDatabaseStorage(
|
||||
$id,
|
||||
$key,
|
||||
$time,
|
||||
$period,
|
||||
$dbForProject,
|
||||
$projectDocuments,
|
||||
$databaseCache,
|
||||
$collectionSizeCache
|
||||
);
|
||||
continue;
|
||||
try {
|
||||
$dbForProject->createDocument('stats', new Document([
|
||||
'$id' => $id,
|
||||
'period' => $period,
|
||||
'time' => $time,
|
||||
'metric' => $key,
|
||||
'value' => $value,
|
||||
'region' => System::getEnv('_APP_REGION', 'default'),
|
||||
]));
|
||||
} catch (Duplicate $th) {
|
||||
if ($value < 0) {
|
||||
$dbForProject->decreaseDocumentAttribute(
|
||||
'stats',
|
||||
$id,
|
||||
'value',
|
||||
abs($value)
|
||||
);
|
||||
} else {
|
||||
$dbForProject->increaseDocumentAttribute(
|
||||
'stats',
|
||||
$id,
|
||||
'value',
|
||||
$value
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$projectDocuments[] = new Document([
|
||||
'$id' => $id,
|
||||
'period' => $period,
|
||||
'time' => $time,
|
||||
'metric' => $key,
|
||||
'value' => $value,
|
||||
'region' => System::getEnv('_APP_REGION', 'default'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
$dbForProject->createOrUpdateDocumentsWithIncrease(
|
||||
collection: 'stats',
|
||||
attribute: 'value',
|
||||
documents: $projectDocuments
|
||||
);
|
||||
|
||||
$end = \microtime(true);
|
||||
Console::log('['.DateTime::now().'] Id: '.$project->getId(). ' InternalId: '.$project->getInternalId(). ' Db: '.$project->getAttribute('database').' ReceivedAt: '.$receivedAt. ' Keys: '.$numberOfKeys. ' Time: '.($end - $start).'s');
|
||||
} catch (\Exception $e) {
|
||||
console::error('[' . DateTime::now() . '] project [' . $project->getInternalId() . '] database [' . $project['database'] . '] ' . ' ' . $e->getMessage());
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
Console::error('[' . DateTime::now() . '] Error processing stats: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private function handleDatabaseStorage(
|
||||
string $id,
|
||||
string $key,
|
||||
?string $time,
|
||||
string $period,
|
||||
Database $dbForProject,
|
||||
array &$projectDocuments,
|
||||
array &$databaseCache,
|
||||
array &$collectionSizeCache,
|
||||
): void {
|
||||
$data = \explode('.', $key);
|
||||
$value = 0;
|
||||
$previousValue = 0;
|
||||
private function handleDatabaseStorage(string $key, Database $dbForProject): void
|
||||
{
|
||||
$data = explode('.', $key);
|
||||
$start = microtime(true);
|
||||
|
||||
try {
|
||||
$previousValue = $dbForProject
|
||||
->getDocument('stats', $id)
|
||||
->getAttribute('value', 0);
|
||||
} catch (\Exception) {
|
||||
// No previous value
|
||||
}
|
||||
$updateMetric = function (Database $dbForProject, int $value, string $key, string $period, string|null $time) {
|
||||
$id = \md5("{$time}_{$period}_{$key}");
|
||||
|
||||
switch (\count($data)) {
|
||||
case METRIC_COLLECTION_LEVEL_STORAGE:
|
||||
$databaseInternalId = $data[0];
|
||||
$collectionInternalId = $data[1];
|
||||
$collectionId = "database_{$databaseInternalId}_collection_{$collectionInternalId}";
|
||||
try {
|
||||
$dbForProject->createDocument('stats', new Document([
|
||||
'$id' => $id,
|
||||
'period' => $period,
|
||||
'time' => $time,
|
||||
'metric' => $key,
|
||||
'value' => $value,
|
||||
'region' => System::getEnv('_APP_REGION', 'default'),
|
||||
]));
|
||||
} catch (Duplicate $th) {
|
||||
if ($value < 0) {
|
||||
$dbForProject->decreaseDocumentAttribute(
|
||||
'stats',
|
||||
$id,
|
||||
'value',
|
||||
abs($value)
|
||||
);
|
||||
} else {
|
||||
$dbForProject->increaseDocumentAttribute(
|
||||
'stats',
|
||||
$id,
|
||||
'value',
|
||||
$value
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
foreach ($this->periods as $period => $format) {
|
||||
$time = 'inf' === $period ? null : date($format, time());
|
||||
$id = \md5("{$time}_{$period}_{$key}");
|
||||
|
||||
$value = 0;
|
||||
$previousValue = 0;
|
||||
try {
|
||||
$previousValue = ($dbForProject->getDocument('stats', $id))->getAttribute('value', 0);
|
||||
} catch (\Exception $e) {
|
||||
// No previous value
|
||||
}
|
||||
|
||||
switch (count($data)) {
|
||||
// Collection Level
|
||||
case METRIC_COLLECTION_LEVEL_STORAGE:
|
||||
Console::log('[' . DateTime::now() . '] Collection Level Storage Calculation [' . $key . ']');
|
||||
$databaseInternalId = $data[0];
|
||||
$collectionInternalId = $data[1];
|
||||
|
||||
if (!isset($collectionSizeCache[$collectionId])) {
|
||||
try {
|
||||
$collectionSizeCache[$collectionId] = $dbForProject->getSizeOfCollection($collectionId);
|
||||
$value = $dbForProject->getSizeOfCollection('database_' . $databaseInternalId . '_collection_' . $collectionInternalId);
|
||||
} catch (\Exception $e) {
|
||||
if (!$e instanceof NotFound) {
|
||||
// Collection not found
|
||||
if ($e->getMessage() !== 'Collection not found') {
|
||||
throw $e;
|
||||
}
|
||||
$collectionSizeCache[$collectionId] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
$value = $collectionSizeCache[$collectionId];
|
||||
// Compare with previous value
|
||||
$diff = $value - $previousValue;
|
||||
|
||||
$diff = $value - $previousValue;
|
||||
if ($diff === 0) {
|
||||
if ($diff === 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Update Collection
|
||||
$updateMetric($dbForProject, $diff, $key, $period, $time);
|
||||
|
||||
// Update Database
|
||||
$databaseKey = str_replace(['{databaseInternalId}'], [$data[0]], METRIC_DATABASE_ID_STORAGE);
|
||||
$updateMetric($dbForProject, $diff, $databaseKey, $period, $time);
|
||||
|
||||
// Update Project
|
||||
$projectKey = METRIC_DATABASES_STORAGE;
|
||||
$updateMetric($dbForProject, $diff, $projectKey, $period, $time);
|
||||
break;
|
||||
}
|
||||
// Database Level
|
||||
case METRIC_DATABASE_LEVEL_STORAGE:
|
||||
Console::log('[' . DateTime::now() . '] Database Level Storage Calculation [' . $key . ']');
|
||||
$databaseInternalId = $data[0];
|
||||
|
||||
$keys = [
|
||||
$key,
|
||||
\str_replace(['{databaseInternalId}'], [$data[0]], METRIC_DATABASE_ID_STORAGE),
|
||||
METRIC_DATABASES_STORAGE
|
||||
];
|
||||
|
||||
foreach ($keys as $metric) {
|
||||
$projectDocuments[] = $this->createStatsDocument($id, $period, $time, $metric, $diff);
|
||||
}
|
||||
break;
|
||||
|
||||
case METRIC_DATABASE_LEVEL_STORAGE:
|
||||
$databaseInternalId = $data[0];
|
||||
$databaseId = "database_{$databaseInternalId}";
|
||||
|
||||
if (!isset($databaseCache[$databaseId])) {
|
||||
$collections = [];
|
||||
try {
|
||||
$databaseCache[$databaseId] = $dbForProject->find($databaseId);
|
||||
$collections = $dbForProject->find('database_' . $databaseInternalId);
|
||||
} catch (\Exception $e) {
|
||||
if (!$e instanceof NotFound) {
|
||||
// Database not found
|
||||
if ($e->getMessage() !== 'Collection not found') {
|
||||
throw $e;
|
||||
}
|
||||
$databaseCache[$databaseId] = [];
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($databaseCache[$databaseId] as $collection) {
|
||||
$collectionId = "{$databaseId}_collection_{$collection->getInternalId()}";
|
||||
|
||||
if (!isset($collectionSizeCache[$collectionId])) {
|
||||
foreach ($collections as $collection) {
|
||||
try {
|
||||
$collectionSizeCache[$collectionId] = $dbForProject->getSizeOfCollection($collectionId);
|
||||
$value += $dbForProject->getSizeOfCollection('database_' . $databaseInternalId . '_collection_' . $collection->getInternalId());
|
||||
} catch (\Exception $e) {
|
||||
if (!$e instanceof NotFound) {
|
||||
// Collection not found
|
||||
if ($e->getMessage() !== 'Collection not found') {
|
||||
throw $e;
|
||||
}
|
||||
$collectionSizeCache[$collectionId] = 0;
|
||||
}
|
||||
}
|
||||
$value += $collectionSizeCache[$collectionId];
|
||||
}
|
||||
|
||||
$diff = $value - $previousValue;
|
||||
if ($diff === 0) {
|
||||
$diff = $value - $previousValue;
|
||||
|
||||
if ($diff === 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Update Database
|
||||
$databaseKey = str_replace(['{databaseInternalId}'], [$data[0]], METRIC_DATABASE_ID_STORAGE);
|
||||
$updateMetric($dbForProject, $diff, $databaseKey, $period, $time);
|
||||
|
||||
// Update Project
|
||||
$projectKey = METRIC_DATABASES_STORAGE;
|
||||
$updateMetric($dbForProject, $diff, $projectKey, $period, $time);
|
||||
break;
|
||||
}
|
||||
// Project Level
|
||||
case METRIC_PROJECT_LEVEL_STORAGE:
|
||||
Console::log('[' . DateTime::now() . '] Project Level Storage Calculation [' . $key . ']');
|
||||
// Get all project databases
|
||||
$databases = $dbForProject->find('database');
|
||||
|
||||
$keys = [
|
||||
\str_replace(['{databaseInternalId}'], [$data[0]], METRIC_DATABASE_ID_STORAGE),
|
||||
METRIC_DATABASES_STORAGE
|
||||
];
|
||||
// Recalculate all databases
|
||||
foreach ($databases as $database) {
|
||||
$collections = $dbForProject->find('database_' . $database->getInternalId());
|
||||
|
||||
foreach ($keys as $metric) {
|
||||
$projectDocuments[] = $this->createStatsDocument($id, $period, $time, $metric, $diff);
|
||||
}
|
||||
break;
|
||||
|
||||
case METRIC_PROJECT_LEVEL_STORAGE:
|
||||
if (!isset($databaseCache['*'])) {
|
||||
try {
|
||||
$databaseCache['*'] = $dbForProject->find('databases');
|
||||
} catch (\Exception $e) {
|
||||
if (!$e instanceof NotFound) {
|
||||
throw $e;
|
||||
}
|
||||
$databaseCache['*'] = [];
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($databaseCache['*'] as $database) {
|
||||
$databaseId = "database_{$database->getInternalId()}";
|
||||
if (!isset($databaseCache[$databaseId])) {
|
||||
try {
|
||||
$databaseCache[$databaseId] = $dbForProject->find($databaseId);
|
||||
} catch (\Exception $e) {
|
||||
if (!$e instanceof NotFound) {
|
||||
throw $e;
|
||||
}
|
||||
$databaseCache[$databaseId] = [];
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($databaseCache[$databaseId] as $collection) {
|
||||
$collectionId = "{$databaseId}_collection_{$collection->getInternalId()}";
|
||||
|
||||
if (!isset($collectionSizeCache[$collectionId])) {
|
||||
foreach ($collections as $collection) {
|
||||
try {
|
||||
$collectionSizeCache[$collectionId] = $dbForProject->getSizeOfCollection($collectionId);
|
||||
$value += $dbForProject->getSizeOfCollection('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId());
|
||||
} catch (\Exception $e) {
|
||||
if (!$e instanceof NotFound) {
|
||||
// Collection not found
|
||||
if ($e->getMessage() !== 'Collection not found') {
|
||||
throw $e;
|
||||
}
|
||||
$collectionSizeCache[$collectionId] = 0;
|
||||
}
|
||||
}
|
||||
$value += $collectionSizeCache[$collectionId];
|
||||
}
|
||||
}
|
||||
|
||||
$diff = $value - $previousValue;
|
||||
if ($diff === 0) {
|
||||
$diff = $value - $previousValue;
|
||||
|
||||
// Update Project
|
||||
$projectKey = METRIC_DATABASES_STORAGE;
|
||||
$updateMetric($dbForProject, $diff, $projectKey, $period, $time);
|
||||
break;
|
||||
}
|
||||
|
||||
$keys = [
|
||||
METRIC_DATABASES_STORAGE
|
||||
];
|
||||
|
||||
foreach ($keys as $metric) {
|
||||
$projectDocuments[] = $this->createStatsDocument($id, $period, $time, $metric, $diff);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function createStatsDocument(
|
||||
string $id,
|
||||
string $period,
|
||||
?string $time,
|
||||
string $key,
|
||||
int $diff,
|
||||
): Document {
|
||||
return new Document([
|
||||
'$id' => $id,
|
||||
'period' => $period,
|
||||
'time' => $time,
|
||||
'metric' => $key,
|
||||
'value' => $diff,
|
||||
'region' => System::getEnv('_APP_REGION', 'default'),
|
||||
]);
|
||||
$end = microtime(true);
|
||||
|
||||
console::log('[' . DateTime::now() . '] DB Storage Calculation [' . $key . '] took ' . (($end - $start) * 1000) . ' milliseconds');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -651,6 +651,200 @@ class UsageTest extends Scope
|
||||
];
|
||||
}
|
||||
|
||||
// /** @depends testDatabaseStoragePrepare */
|
||||
// #[Retry(count: 1)]
|
||||
// public function testDatabaseStorageStatsCreateDocument(array $data): array
|
||||
// {
|
||||
// $databaseId = $data['databaseId'];
|
||||
// $collectionId = $data['collectionId'];
|
||||
|
||||
// $originalProjectMetrics = $this->client->call(
|
||||
// Client::METHOD_GET,
|
||||
// '/project/usage',
|
||||
// $this->getConsoleHeaders(),
|
||||
// [
|
||||
// 'period' => '1d',
|
||||
// 'startDate' => self::getToday(),
|
||||
// 'endDate' => self::getTomorrow(),
|
||||
// ]
|
||||
// );
|
||||
|
||||
// $this->assertEquals(200, $originalProjectMetrics['headers']['status-code']);
|
||||
// $this->assertArrayHasKey('databasesStorageTotal', $originalProjectMetrics['body']);
|
||||
|
||||
// $originalProjectMetrics = $originalProjectMetrics['body'];
|
||||
|
||||
// $originalDatabaseMetrics = $this->client->call(
|
||||
// Client::METHOD_GET,
|
||||
// '/databases/' . $databaseId . '/usage?range=30d',
|
||||
// $this->getConsoleHeaders()
|
||||
// );
|
||||
|
||||
// $this->assertEquals(200, $originalDatabaseMetrics['headers']['status-code']);
|
||||
// $this->assertArrayHasKey('storageTotal', $originalDatabaseMetrics['body']);
|
||||
// $originalDatabaseMetrics = $originalDatabaseMetrics['body'];
|
||||
|
||||
// // Create documents
|
||||
// for ($i = 0; $i < 100; $i++) {
|
||||
// $response = $this->client->call(
|
||||
// Client::METHOD_POST,
|
||||
// '/databases/' . $databaseId . '/collections/' . $collectionId . '/documents',
|
||||
// array_merge([
|
||||
// 'content-type' => 'application/json',
|
||||
// 'x-appwrite-project' => $this->getProject()['$id']
|
||||
// ], $this->getHeaders()),
|
||||
// [
|
||||
// 'documentId' => 'unique()',
|
||||
// 'data' => ['data' => str_repeat('a', 10000)],
|
||||
// ]
|
||||
// );
|
||||
|
||||
// $this->assertEquals(201, $response['headers']['status-code']);
|
||||
// }
|
||||
|
||||
// sleep(self::WAIT);
|
||||
|
||||
// for ($i = 0; $i < 3; $i++) {
|
||||
// try {
|
||||
// $newProjectMetrics = $this->client->call(
|
||||
// Client::METHOD_GET,
|
||||
// '/project/usage',
|
||||
// $this->getConsoleHeaders(),
|
||||
// [
|
||||
// 'period' => '1d',
|
||||
// 'startDate' => self::getToday(),
|
||||
// 'endDate' => self::getTomorrow(),
|
||||
// ]
|
||||
// );
|
||||
|
||||
// $this->assertEquals(200, $newProjectMetrics['headers']['status-code']);
|
||||
// $this->assertArrayHasKey('databasesStorageTotal', $newProjectMetrics['body']);
|
||||
// $this->assertGreaterThan($originalProjectMetrics['databasesStorageTotal'], $newProjectMetrics['body']['databasesStorageTotal']);
|
||||
|
||||
// $newProjectMetrics = $newProjectMetrics['body'];
|
||||
|
||||
// $newDatabaseMetrics = $this->client->call(
|
||||
// Client::METHOD_GET,
|
||||
// '/databases/' . $databaseId . '/usage?range=30d',
|
||||
// $this->getConsoleHeaders()
|
||||
// );
|
||||
|
||||
// $this->assertEquals(200, $newDatabaseMetrics['headers']['status-code']);
|
||||
// $this->assertArrayHasKey('storageTotal', $newDatabaseMetrics['body']);
|
||||
// $this->assertGreaterThan($originalDatabaseMetrics['storageTotal'], $newDatabaseMetrics['body']['storageTotal']);
|
||||
|
||||
// $newDatabaseMetrics = $newDatabaseMetrics['body'];
|
||||
|
||||
// return [
|
||||
// 'databaseId' => $databaseId,
|
||||
// 'collectionId' => $collectionId,
|
||||
// 'currentProjectMetrics' => $newProjectMetrics,
|
||||
// 'currentDatabaseMetrics' => $newDatabaseMetrics,
|
||||
// ];
|
||||
// } catch (ExpectationFailedException $e) {
|
||||
// if ($i === 2) {
|
||||
// throw $e;
|
||||
// }
|
||||
// sleep(self::WAIT);
|
||||
// continue;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// /** @depends testDatabaseStorageStatsCreateDocument */
|
||||
// #[Retry(count: 1)]
|
||||
// public function testDatabaseStorageStatsDeleteDocument(array $data): array
|
||||
// {
|
||||
// $databaseId = $data['databaseId'];
|
||||
// $collectionId = $data['collectionId'];
|
||||
// $currentProjectMetrics = $data['currentProjectMetrics'];
|
||||
// $currentDatabaseMetrics = $data['currentDatabaseMetrics'];
|
||||
|
||||
// $documents = $this->client->call(
|
||||
// Client::METHOD_GET,
|
||||
// '/databases/' . $databaseId . '/collections/' . $collectionId . '/documents',
|
||||
// array_merge([
|
||||
// 'x-appwrite-project' => $this->getProject()['$id']
|
||||
// ], $this->getHeaders()),
|
||||
// [
|
||||
// 'queries' => [
|
||||
// Query::limit(50)->toString()
|
||||
// ]
|
||||
// ]
|
||||
// );
|
||||
|
||||
// foreach ($documents['body']['documents'] as $document) {
|
||||
// $response = $this->client->call(
|
||||
// Client::METHOD_DELETE,
|
||||
// '/databases/' . $databaseId . '/collections/' . $collectionId . '/documents/' . $document['$id'],
|
||||
// array_merge([
|
||||
// 'x-appwrite-project' => $this->getProject()['$id']
|
||||
// ], $this->getHeaders())
|
||||
// );
|
||||
|
||||
// $this->assertEquals(204, $response['headers']['status-code']);
|
||||
// }
|
||||
|
||||
// sleep(self::WAIT);
|
||||
|
||||
// for ($i = 0; $i < 3; $i++) {
|
||||
// try {
|
||||
// $newProjectMetrics = $this->client->call(
|
||||
// Client::METHOD_GET,
|
||||
// '/project/usage',
|
||||
// $this->getConsoleHeaders(),
|
||||
// [
|
||||
// 'period' => '1d',
|
||||
// 'startDate' => self::getToday(),
|
||||
// 'endDate' => self::getTomorrow(),
|
||||
// ]
|
||||
// );
|
||||
|
||||
// $this->assertEquals(200, $newProjectMetrics['headers']['status-code']);
|
||||
// $this->assertArrayHasKey('databasesStorageTotal', $newProjectMetrics['body']);
|
||||
// $this->assertLessThan($currentProjectMetrics['databasesStorageTotal'], $newProjectMetrics['body']['databasesStorageTotal']);
|
||||
|
||||
// $newProjectMetrics = $newProjectMetrics['body'];
|
||||
|
||||
// $newDatabaseMetrics = $this->client->call(
|
||||
// Client::METHOD_GET,
|
||||
// '/databases/' . $databaseId . '/usage?range=30d',
|
||||
// $this->getConsoleHeaders()
|
||||
// );
|
||||
|
||||
// $this->assertEquals(200, $newDatabaseMetrics['headers']['status-code']);
|
||||
// $this->assertArrayHasKey('storageTotal', $newDatabaseMetrics['body']);
|
||||
// $this->assertLessThan($currentDatabaseMetrics['storageTotal'], $newDatabaseMetrics['body']['storageTotal']);
|
||||
|
||||
// $newDatabaseMetrics = $newDatabaseMetrics['body'];
|
||||
|
||||
// return [
|
||||
// 'databaseId' => $databaseId,
|
||||
// 'collectionId' => $collectionId,
|
||||
// 'currentProjectMetrics' => $newProjectMetrics,
|
||||
// 'currentDatabaseMetrics' => $newDatabaseMetrics,
|
||||
// ];
|
||||
// } catch (ExpectationFailedException $e) {
|
||||
// if ($i === 2) {
|
||||
// throw $e;
|
||||
// }
|
||||
// sleep(self::WAIT);
|
||||
// continue;
|
||||
// }
|
||||
// }
|
||||
|
||||
// $newProjectMetrics = $this->client->call(
|
||||
// Client::METHOD_GET,
|
||||
// '/project/usage',
|
||||
// $this->getConsoleHeaders(),
|
||||
// [
|
||||
// 'period' => '1d',
|
||||
// 'startDate' => self::getToday(),
|
||||
// 'endDate' => self::getTomorrow(),
|
||||
// ]
|
||||
// );
|
||||
// }
|
||||
|
||||
/** @depends testDatabaseStats */
|
||||
public function testPrepareFunctionsStats(array $data): array
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user