mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Merge branch '1.8.x' into feat-health-module
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user