mirror of
https://github.com/appwrite/appwrite
synced 2026-05-23 08:58:35 +00:00
Merge pull request #11069 from appwrite/storage-size
This commit is contained in:
commit
8bb6f01e22
12 changed files with 162 additions and 16 deletions
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"openapi": "3.0.0",
|
||||
"info": {
|
||||
"version": "1.8.0",
|
||||
"version": "1.8.1",
|
||||
"title": "Appwrite",
|
||||
"description": "Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https:\/\/appwrite.io\/docs](https:\/\/appwrite.io\/docs)",
|
||||
"termsOfService": "https:\/\/appwrite.io\/policy\/terms",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"openapi": "3.0.0",
|
||||
"info": {
|
||||
"version": "1.8.0",
|
||||
"version": "1.8.1",
|
||||
"title": "Appwrite",
|
||||
"description": "Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https:\/\/appwrite.io\/docs](https:\/\/appwrite.io\/docs)",
|
||||
"termsOfService": "https:\/\/appwrite.io\/policy\/terms",
|
||||
|
|
@ -55086,6 +55086,12 @@
|
|||
"type": "boolean",
|
||||
"description": "Image transformations are enabled.",
|
||||
"x-example": false
|
||||
},
|
||||
"totalSize": {
|
||||
"type": "integer",
|
||||
"description": "Total size of this bucket in bytes.",
|
||||
"x-example": 128,
|
||||
"format": "int32"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
|
@ -55101,7 +55107,8 @@
|
|||
"compression",
|
||||
"encryption",
|
||||
"antivirus",
|
||||
"transformations"
|
||||
"transformations",
|
||||
"totalSize"
|
||||
],
|
||||
"example": {
|
||||
"$id": "5e5ea5c16897e",
|
||||
|
|
@ -55121,7 +55128,8 @@
|
|||
"compression": "gzip",
|
||||
"encryption": false,
|
||||
"antivirus": false,
|
||||
"transformations": false
|
||||
"transformations": false,
|
||||
"totalSize": 128
|
||||
}
|
||||
},
|
||||
"resourceToken": {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"openapi": "3.0.0",
|
||||
"info": {
|
||||
"version": "1.8.0",
|
||||
"version": "1.8.1",
|
||||
"title": "Appwrite",
|
||||
"description": "Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https:\/\/appwrite.io\/docs](https:\/\/appwrite.io\/docs)",
|
||||
"termsOfService": "https:\/\/appwrite.io\/policy\/terms",
|
||||
|
|
@ -43237,6 +43237,12 @@
|
|||
"type": "boolean",
|
||||
"description": "Image transformations are enabled.",
|
||||
"x-example": false
|
||||
},
|
||||
"totalSize": {
|
||||
"type": "integer",
|
||||
"description": "Total size of this bucket in bytes.",
|
||||
"x-example": 128,
|
||||
"format": "int32"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
|
@ -43252,7 +43258,8 @@
|
|||
"compression",
|
||||
"encryption",
|
||||
"antivirus",
|
||||
"transformations"
|
||||
"transformations",
|
||||
"totalSize"
|
||||
],
|
||||
"example": {
|
||||
"$id": "5e5ea5c16897e",
|
||||
|
|
@ -43272,7 +43279,8 @@
|
|||
"compression": "gzip",
|
||||
"encryption": false,
|
||||
"antivirus": false,
|
||||
"transformations": false
|
||||
"transformations": false,
|
||||
"totalSize": 128
|
||||
}
|
||||
},
|
||||
"resourceToken": {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"version": "1.8.0",
|
||||
"version": "1.8.1",
|
||||
"title": "Appwrite",
|
||||
"description": "Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https:\/\/appwrite.io\/docs](https:\/\/appwrite.io\/docs)",
|
||||
"termsOfService": "https:\/\/appwrite.io\/policy\/terms",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"version": "1.8.0",
|
||||
"version": "1.8.1",
|
||||
"title": "Appwrite",
|
||||
"description": "Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https:\/\/appwrite.io\/docs](https:\/\/appwrite.io\/docs)",
|
||||
"termsOfService": "https:\/\/appwrite.io\/policy\/terms",
|
||||
|
|
@ -54918,6 +54918,12 @@
|
|||
"type": "boolean",
|
||||
"description": "Image transformations are enabled.",
|
||||
"x-example": false
|
||||
},
|
||||
"totalSize": {
|
||||
"type": "integer",
|
||||
"description": "Total size of this bucket in bytes.",
|
||||
"x-example": 128,
|
||||
"format": "int32"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
|
@ -54933,7 +54939,8 @@
|
|||
"compression",
|
||||
"encryption",
|
||||
"antivirus",
|
||||
"transformations"
|
||||
"transformations",
|
||||
"totalSize"
|
||||
],
|
||||
"example": {
|
||||
"$id": "5e5ea5c16897e",
|
||||
|
|
@ -54953,7 +54960,8 @@
|
|||
"compression": "gzip",
|
||||
"encryption": false,
|
||||
"antivirus": false,
|
||||
"transformations": false
|
||||
"transformations": false,
|
||||
"totalSize": 128
|
||||
}
|
||||
},
|
||||
"resourceToken": {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"version": "1.8.0",
|
||||
"version": "1.8.1",
|
||||
"title": "Appwrite",
|
||||
"description": "Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https:\/\/appwrite.io\/docs](https:\/\/appwrite.io\/docs)",
|
||||
"termsOfService": "https:\/\/appwrite.io\/policy\/terms",
|
||||
|
|
@ -43165,6 +43165,12 @@
|
|||
"type": "boolean",
|
||||
"description": "Image transformations are enabled.",
|
||||
"x-example": false
|
||||
},
|
||||
"totalSize": {
|
||||
"type": "integer",
|
||||
"description": "Total size of this bucket in bytes.",
|
||||
"x-example": 128,
|
||||
"format": "int32"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
|
@ -43180,7 +43186,8 @@
|
|||
"compression",
|
||||
"encryption",
|
||||
"antivirus",
|
||||
"transformations"
|
||||
"transformations",
|
||||
"totalSize"
|
||||
],
|
||||
"example": {
|
||||
"$id": "5e5ea5c16897e",
|
||||
|
|
@ -43200,7 +43207,8 @@
|
|||
"compression": "gzip",
|
||||
"encryption": false,
|
||||
"antivirus": false,
|
||||
"transformations": false
|
||||
"transformations": false,
|
||||
"totalSize": 128
|
||||
}
|
||||
},
|
||||
"resourceToken": {
|
||||
|
|
|
|||
|
|
@ -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,55 @@ 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);
|
||||
}
|
||||
|
||||
$dbForLogs = call_user_func($getLogsDB, $project);
|
||||
$this->addBucketStorageSize($dbForLogs, $bucket);
|
||||
|
||||
$response->dynamic($bucket, Response::MODEL_BUCKET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the latest aggregated bucket storage size from logs DB stats.
|
||||
*/
|
||||
private function addBucketStorageSize(Database $dbForLogs, Document $bucket): void
|
||||
{
|
||||
$metric = str_replace(
|
||||
'{bucketInternalId}',
|
||||
$bucket->getSequence(),
|
||||
METRIC_BUCKET_ID_FILES_STORAGE
|
||||
);
|
||||
|
||||
$statsDocId = md5('_inf_' . $metric);
|
||||
$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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2344,6 +2344,7 @@ trait Base
|
|||
_id
|
||||
name
|
||||
enabled
|
||||
totalSize
|
||||
}
|
||||
}';
|
||||
case self::UPDATE_BUCKET:
|
||||
|
|
|
|||
|
|
@ -110,7 +110,9 @@ class StorageServerTest extends Scope
|
|||
|
||||
/**
|
||||
* @depends testCreateBucket
|
||||
* @depends testCreateFile
|
||||
* @param $bucket
|
||||
* @param $file
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
|
|
@ -134,6 +136,7 @@ class StorageServerTest extends Scope
|
|||
$this->assertArrayNotHasKey('errors', $bucket['body']);
|
||||
$bucket = $bucket['body']['data']['storageGetBucket'];
|
||||
$this->assertEquals('Actors', $bucket['name']);
|
||||
$this->assertArrayHasKey('totalSize', $bucket);
|
||||
|
||||
return $bucket;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -951,4 +951,69 @@ trait StorageBase
|
|||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function testBucketTotalSize(): void
|
||||
{
|
||||
$bucket = $this->client->call(Client::METHOD_POST, '/storage/buckets', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
], [
|
||||
'bucketId' => ID::unique(),
|
||||
'name' => 'Test Bucket Size',
|
||||
'permissions' => [
|
||||
Permission::read(Role::any()),
|
||||
Permission::create(Role::any()),
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $bucket['headers']['status-code']);
|
||||
$bucketId = $bucket['body']['$id'];
|
||||
|
||||
// bucket should have totalSize = 0 (no files)
|
||||
$emptyBucket = $this->client->call(Client::METHOD_GET, '/storage/buckets/' . $bucketId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $emptyBucket['headers']['status-code']);
|
||||
$this->assertArrayHasKey('totalSize', $emptyBucket['body']);
|
||||
$this->assertEquals(0, $emptyBucket['body']['totalSize']);
|
||||
|
||||
// upload first file
|
||||
$file1 = $this->client->call(Client::METHOD_POST, '/storage/buckets/' . $bucketId . '/files', array_merge([
|
||||
'content-type' => 'multipart/form-data',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'fileId' => ID::unique(),
|
||||
'file' => new CURLFile(realpath(__DIR__ . '/../../../resources/logo.png'), 'image/png', 'logo.png'),
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $file1['headers']['status-code']);
|
||||
|
||||
// upload second file
|
||||
$file2 = $this->client->call(Client::METHOD_POST, '/storage/buckets/' . $bucketId . '/files', array_merge([
|
||||
'content-type' => 'multipart/form-data',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'fileId' => ID::unique(),
|
||||
'file' => new CURLFile(realpath(__DIR__ . '/../../../resources/image.webp'), 'image/webp', 'image.webp'),
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $file2['headers']['status-code']);
|
||||
|
||||
$bucket = $this->client->call(Client::METHOD_GET, '/storage/buckets/' . $bucketId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $bucket['headers']['status-code']);
|
||||
$this->assertArrayHasKey('totalSize', $bucket['body']);
|
||||
$this->assertIsInt($bucket['body']['totalSize']);
|
||||
|
||||
/* will always be 0 in tests because the worker runs hourly! */
|
||||
$this->assertGreaterThanOrEqual(0, $bucket['body']['totalSize']);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -186,6 +186,7 @@ class StorageCustomServerTest extends Scope
|
|||
$this->assertNotEmpty($response['body']);
|
||||
$this->assertEquals($id, $response['body']['$id']);
|
||||
$this->assertEquals('Test Bucket', $response['body']['name']);
|
||||
$this->assertArrayHasKey('totalSize', $response['body']);
|
||||
|
||||
/**
|
||||
* Test for FAILURE
|
||||
|
|
|
|||
Loading…
Reference in a new issue