Merge branch '1.8.x' into feat-health-module

This commit is contained in:
Damodar Lohani
2026-01-08 11:26:56 +05:45
committed by GitHub
23 changed files with 1309 additions and 811 deletions
@@ -68,7 +68,7 @@ class Create extends Action
->param('enabled', true, new Boolean(true), 'Is bucket enabled? When set to \'disabled\', users cannot access the files in this bucket but Server SDKs with and API key can still access the bucket. No files are lost when this is toggled.', true)
->param('maximumFileSize', fn (array $plan) => empty($plan['fileSize']) ? (int) System::getEnv('_APP_STORAGE_LIMIT', 0) : $plan['fileSize'] * 1000 * 1000, fn (array $plan) => new Range(1, empty($plan['fileSize']) ? (int) System::getEnv('_APP_STORAGE_LIMIT', 0) : $plan['fileSize'] * 1000 * 1000), 'Maximum file size allowed in bytes. Maximum allowed value is ' . Storage::human(System::getEnv('_APP_STORAGE_LIMIT', 0), 0) . '.', true, ['plan'])
->param('allowedFileExtensions', [], new ArrayList(new Text(64), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Allowed file extensions. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' extensions are allowed, each 64 characters long.', true)
->param('compression', Compression::NONE, new WhiteList([Compression::NONE, Compression::GZIP, Compression::ZSTD], true), 'Compression algorithm choosen for compression. Can be one of ' . Compression::NONE . ', [' . Compression::GZIP . '](https://en.wikipedia.org/wiki/Gzip), or [' . Compression::ZSTD . '](https://en.wikipedia.org/wiki/Zstd), For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' compression is skipped even if it\'s enabled', true)
->param('compression', Compression::NONE, new WhiteList([Compression::NONE, Compression::GZIP, Compression::ZSTD], true), 'Compression algorithm chosen for compression. Can be one of ' . Compression::NONE . ', [' . Compression::GZIP . '](https://en.wikipedia.org/wiki/Gzip), or [' . Compression::ZSTD . '](https://en.wikipedia.org/wiki/Zstd), For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' compression is skipped even if it\'s enabled', true)
->param('encryption', true, new Boolean(true), 'Is encryption enabled? For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' encryption is skipped even if it\'s enabled', true)
->param('antivirus', true, new Boolean(true), 'Is virus scanning enabled? For file size above ' . Storage::human(APP_LIMIT_ANTIVIRUS, 0) . ' AntiVirus scanning is skipped even if it\'s enabled', true)
->param('transformations', true, new Boolean(true), 'Are image transformations enabled?', true)
@@ -8,6 +8,9 @@ use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
@@ -46,20 +49,48 @@ class Get extends Action
->param('bucketId', '', new UID(), 'Bucket unique ID.')
->inject('response')
->inject('dbForProject')
->inject('project')
->inject('getLogsDB')
->callback($this->action(...));
}
public function action(
string $bucketId,
Response $response,
Database $dbForProject
) {
Database $dbForProject,
Document $project,
callable $getLogsDB
): void {
$bucket = $dbForProject->getDocument('buckets', $bucketId);
if ($bucket->isEmpty()) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
}
$metric = str_replace(
'{bucketInternalId}',
$bucket->getSequence(),
METRIC_BUCKET_ID_FILES_STORAGE
);
$statsDocId = md5('_inf_' . $metric);
$dbForLogs = call_user_func($getLogsDB, $project);
$storageStats = Authorization::skip(
fn () => $dbForLogs->getDocument(
'stats',
$statsDocId,
[Query::select(['value'])]
)
);
/**
* The value can be 0 if stats were not aggregated when this request was made!
*/
$totalSize = $storageStats->isEmpty() ? 0 : $storageStats->getAttribute('value', 0);
$bucket->setAttribute('totalSize', $totalSize);
$response->dynamic($bucket, Response::MODEL_BUCKET);
}
}
@@ -65,7 +65,7 @@ class Update extends Action
->param('enabled', true, new Boolean(true), 'Is bucket enabled? When set to \'disabled\', users cannot access the files in this bucket but Server SDKs with and API key can still access the bucket. No files are lost when this is toggled.', true)
->param('maximumFileSize', fn (array $plan) => empty($plan['fileSize']) ? (int) System::getEnv('_APP_STORAGE_LIMIT', 0) : $plan['fileSize'] * 1000 * 1000, fn (array $plan) => new Range(1, empty($plan['fileSize']) ? (int) System::getEnv('_APP_STORAGE_LIMIT', 0) : $plan['fileSize'] * 1000 * 1000), 'Maximum file size allowed in bytes. Maximum allowed value is ' . Storage::human(System::getEnv('_APP_STORAGE_LIMIT', 0), 0) . '.', true, ['plan'])
->param('allowedFileExtensions', [], new ArrayList(new Text(64), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Allowed file extensions. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' extensions are allowed, each 64 characters long.', true)
->param('compression', Compression::NONE, new WhiteList([Compression::NONE, Compression::GZIP, Compression::ZSTD], true), 'Compression algorithm choosen for compression. Can be one of ' . Compression::NONE . ', [' . Compression::GZIP . '](https://en.wikipedia.org/wiki/Gzip), or [' . Compression::ZSTD . '](https://en.wikipedia.org/wiki/Zstd), For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' compression is skipped even if it\'s enabled', true)
->param('compression', Compression::NONE, new WhiteList([Compression::NONE, Compression::GZIP, Compression::ZSTD], true), 'Compression algorithm chosen for compression. Can be one of ' . Compression::NONE . ', [' . Compression::GZIP . '](https://en.wikipedia.org/wiki/Gzip), or [' . Compression::ZSTD . '](https://en.wikipedia.org/wiki/Zstd), For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' compression is skipped even if it\'s enabled', true)
->param('encryption', true, new Boolean(true), 'Is encryption enabled? For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' encryption is skipped even if it\'s enabled', true)
->param('antivirus', true, new Boolean(true), 'Is virus scanning enabled? For file size above ' . Storage::human(APP_LIMIT_ANTIVIRUS, 0) . ' AntiVirus scanning is skipped even if it\'s enabled', true)
->param('transformations', true, new Boolean(true), 'Are image transformations enabled?', true)
@@ -13,6 +13,7 @@ use Utopia\Database\Document;
use Utopia\Database\Exception\Order as OrderException;
use Utopia\Database\Exception\Query as QueryException;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Query\Cursor;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
@@ -55,6 +56,8 @@ class XList extends Action
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
->inject('response')
->inject('dbForProject')
->inject('project')
->inject('getLogsDB')
->callback($this->action(...));
}
@@ -63,7 +66,9 @@ class XList extends Action
string $search,
bool $includeTotal,
Response $response,
Database $dbForProject
Database $dbForProject,
Document $project,
callable $getLogsDB
) {
try {
$queries = Query::parseQueries($queries);
@@ -109,6 +114,45 @@ class XList extends Action
} catch (QueryException $e) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
if (!empty($buckets)) {
$bucketByStatsId = [];
$dbForLogs = call_user_func($getLogsDB, $project);
foreach ($buckets as $bucket) {
$metric = str_replace(
'{bucketInternalId}',
$bucket->getSequence(),
METRIC_BUCKET_ID_FILES_STORAGE
);
$statId = md5('_inf_' . $metric);
$bucketByStatsId[$statId] = $bucket;
// set a default
$bucket->setAttribute('totalSize', 0);
}
/* @type Document[] $stats */
$stats = Authorization::skip(function () use ($dbForLogs, $bucketByStatsId) {
$statsIds = array_keys($bucketByStatsId);
return $dbForLogs->find('stats', [
Query::equal('$id', $statsIds),
Query::select(['value']),
]);
});
foreach ($stats as $stat) {
$bucket = $bucketByStatsId[$stat->getId()];
if ($bucket) {
$bucket->setAttribute('totalSize', $stat->getAttribute('value', 0));
}
}
}
$response->dynamic(new Document([
'buckets' => $buckets,
'total' => $total,
+1 -1
View File
@@ -187,7 +187,7 @@ class Deletes extends Action
case DELETE_TYPE_MAINTENANCE:
$this->deleteExpiredTargets($project, $getProjectDB);
$this->deleteExecutionLogs($project, $getProjectDB, $executionRetention);
$this->deleteAuditLogs($project, $getProjectDB, $auditRetention);
$this->deleteAuditLogs($project, $getAudit, $auditRetention);
$this->deleteUsageStats($project, $getProjectDB, $getLogsDB, $hourlyUsageRetentionDatetime);
$this->deleteExpiredSessions($project, $getProjectDB);
$this->deleteExpiredTransactions($project, $getProjectDB);
+53 -47
View File
@@ -110,10 +110,18 @@ class Migrations extends Action
$events = $payload['events'] ?? [];
$migration = new Document($payload['migration'] ?? []);
if ($migration->isEmpty()) {
throw new \Exception('Migration not found');
}
if ($project->getId() === 'console') {
return;
}
if ($project->isEmpty()) {
throw new \Exception('Project not found');
}
$this->dbForProject = $dbForProject;
$this->dbForPlatform = $dbForPlatform;
$this->project = $project;
@@ -312,7 +320,8 @@ class Migrations extends Action
Mail $queueForMails,
array $platform,
): void {
$project = $this->dbForPlatform->getDocument('projects', $this->project->getId());
$project = $this->project;
$tempAPIKey = $this->generateAPIKey($project);
$transfer = $source = $destination = null;
@@ -386,62 +395,59 @@ class Migrations extends Action
Console::error('Line: ' . $th->getLine());
Console::error($th->getTraceAsString());
if (! $migration->isEmpty()) {
$migration->setAttribute('status', 'failed');
$migration->setAttribute('stage', 'finished');
$migration->setAttribute('status', 'failed');
$migration->setAttribute('stage', 'finished');
call_user_func($this->logError, $th, 'appwrite-worker', 'appwrite-queue-'.self::getName(), [
'migrationId' => $migration->getId(),
'source' => $migration->getAttribute('source') ?? '',
'destination' => $migration->getAttribute('destination') ?? '',
]);
call_user_func($this->logError, $th, 'appwrite-worker', 'appwrite-queue-'.self::getName(), [
'migrationId' => $migration->getId(),
'source' => $migration->getAttribute('source') ?? '',
'destination' => $migration->getAttribute('destination') ?? '',
]);
return;
}
if ($transfer) {
$sourceErrors = $source->getErrors();
$destinationErrors = $destination->getErrors();
$migration->setAttribute('errors', $this->sanitizeErrors($sourceErrors, $destinationErrors));
}
} finally {
$this->updateMigrationDocument($migration, $project, $queueForRealtime);
try {
$this->updateMigrationDocument($migration, $project, $queueForRealtime);
if ($migration->getAttribute('status', '') === 'failed') {
Console::error('Migration('.$migration->getSequence().':'.$migration->getId().') failed, Project('.$this->project->getSequence().':'.$this->project->getId().')');
if ($migration->getAttribute('status', '') === 'failed') {
Console::error('Migration('.$migration->getSequence().':'.$migration->getId().') failed, Project('.$this->project->getSequence().':'.$this->project->getId().')');
$sourceErrors = $source?->getErrors() ?? [];
$destinationErrors = $destination?->getErrors() ?? [];
$sourceErrors = $source?->getErrors() ?? [];
$destinationErrors = $destination?->getErrors() ?? [];
foreach ([...$sourceErrors, ...$destinationErrors] as $error) {
/** @var MigrationException $error */
if ($error->getCode() === 0 || $error->getCode() >= 500) {
($this->logError)($error, 'appwrite-worker', 'appwrite-queue-' . self::getName(), [
'migrationId' => $migration->getId(),
'source' => $migration->getAttribute('source') ?? '',
'destination' => $migration->getAttribute('destination') ?? '',
'resourceName' => $error->getResourceName(),
'resourceGroup' => $error->getResourceGroup(),
]);
foreach ([...$sourceErrors, ...$destinationErrors] as $error) {
/** @var MigrationException $error */
if ($error->getCode() === 0 || $error->getCode() >= 500) {
($this->logError)($error, 'appwrite-worker', 'appwrite-queue-' . self::getName(), [
'migrationId' => $migration->getId(),
'source' => $migration->getAttribute('source') ?? '',
'destination' => $migration->getAttribute('destination') ?? '',
'resourceName' => $error->getResourceName(),
'resourceGroup' => $error->getResourceGroup(),
]);
}
}
$source?->error();
$destination?->error();
}
if ($migration->getAttribute('status', '') === 'completed') {
$destination?->success();
$source?->success();
// todo: Move to CSV hook
if ($migration->getAttribute('destination') === DestinationCSV::getName()) {
$this->handleCSVExportComplete($project, $migration, $queueForMails, $queueForRealtime, $platform);
}
}
} finally {
$source?->cleanUp();
$destination?->cleanUp();
$source?->error();
$destination?->error();
$transfer = null;
$source = null;
$destination = null;
}
if ($migration->getAttribute('status', '') === 'completed') {
$destination?->success();
$source?->success();
if ($migration->getAttribute('destination') === DestinationCSV::getName()) {
$this->handleCSVExportComplete($project, $migration, $queueForMails, $queueForRealtime, $platform);
}
}
$transfer = null;
$source = null;
$destination = null;
}
}
@@ -8,6 +8,7 @@ class Projects extends Base
'name',
'teamId',
'labels',
'search'
];
/**
@@ -69,7 +69,7 @@ class Bucket extends Model
])
->addRule('compression', [
'type' => self::TYPE_STRING,
'description' => 'Compression algorithm choosen for compression. Will be one of ' . Compression::NONE . ', [' . Compression::GZIP . '](https://en.wikipedia.org/wiki/Gzip), or [' . Compression::ZSTD . '](https://en.wikipedia.org/wiki/Zstd).',
'description' => 'Compression algorithm chosen for compression. Will be one of ' . Compression::NONE . ', [' . Compression::GZIP . '](https://en.wikipedia.org/wiki/Gzip), or [' . Compression::ZSTD . '](https://en.wikipedia.org/wiki/Zstd).',
'default' => '',
'example' => 'gzip',
'array' => false
@@ -92,6 +92,12 @@ class Bucket extends Model
'default' => true,
'example' => false,
])
->addRule('totalSize', [
'type' => self::TYPE_INTEGER,
'description' => 'Total size of this bucket in bytes.',
'default' => 0,
'example' => 128,
])
;
}