1
0
Fork 0
mirror of synced 2024-06-02 10:54:44 +12:00
appwrite/app/controllers/api/storage.php

1552 lines
68 KiB
PHP
Raw Normal View History

2019-05-09 18:54:39 +12:00
<?php
2020-06-29 05:31:21 +12:00
use Utopia\App;
2019-05-09 18:54:39 +12:00
use Utopia\Cache\Adapter\Filesystem;
use Appwrite\ClamAV\Network;
use Utopia\Database\Validator\Authorization;
2021-07-21 20:13:00 +12:00
use Appwrite\Database\Validator\CustomId;
2021-05-03 20:28:31 +12:00
use Utopia\Database\Document;
2021-07-07 22:07:11 +12:00
use Utopia\Database\Query;
use Utopia\Exception;
2021-07-26 02:47:18 +12:00
use Utopia\Database\Validator\UID;
2021-01-22 21:28:33 +13:00
use Utopia\Storage\Storage;
use Utopia\Storage\Validator\File;
2021-07-07 22:07:11 +12:00
use Utopia\Storage\Validator\FileExt;
2021-01-22 21:28:33 +13:00
use Utopia\Storage\Validator\FileSize;
use Utopia\Storage\Validator\Upload;
use Utopia\Storage\Compression\Algorithms\GZIP;
2021-02-20 02:59:46 +13:00
use Utopia\Image\Image;
use Appwrite\OpenSSL\OpenSSL;
2020-10-31 08:53:27 +13:00
use Appwrite\Utopia\Response;
use Utopia\Cache\Cache;
2020-06-30 09:43:34 +12:00
use Utopia\Config\Config;
2021-09-02 22:49:07 +12:00
use Utopia\Database\Database;
use Utopia\Database\Exception\Duplicate;
2021-07-07 22:07:11 +12:00
use Utopia\Validator\ArrayList;
use Utopia\Validator\Boolean;
use Utopia\Validator\HexColor;
2021-06-14 23:59:47 +12:00
use Utopia\Validator\Integer;
2021-07-07 22:07:11 +12:00
use Utopia\Validator\Text;
use Utopia\Validator\WhiteList;
use Utopia\Validator\Range;
2021-09-30 19:23:19 +13:00
use Utopia\Database\Validator\Permissions;
2021-09-12 21:29:40 +12:00
2021-06-14 23:32:09 +12:00
App::post('/v1/storage/buckets')
->desc('Create storage bucket')
->groups(['api', 'storage'])
->label('scope', 'buckets.write')
->label('event', 'storage.buckets.create')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
2021-06-14 23:32:09 +12:00
->label('sdk.namespace', 'storage')
->label('sdk.method', 'createBucket')
->label('sdk.description', '/docs/references/storage/create-bucket.md')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_BUCKET)
2021-09-02 20:30:10 +12:00
->param('bucketId', '', new CustomId(), 'Unique Id. Choose your own unique ID or pass the string `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.')
2021-06-14 23:59:47 +12:00
->param('name', '', new Text(128), 'Bucket name', false)
->param('permission', null, new WhiteList(['file', 'bucket']), 'Permissions type model to use for reading files in this bucket. You can use bucket-level permission set once on the bucket using the `read` and `write` params, or you can set file-level permission where each file read and write params will decide who has access to read and write to each file individually. [learn more about permissions](/docs/permissions) and get a full list of available permissions.')
2021-09-30 19:23:19 +13:00
->param('read', null, new Permissions(), 'An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.', true)
->param('write', null, new Permissions(), 'An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.', true)
->param('maximumFileSize', (int) App::getEnv('_APP_STORAGE_LIMIT', 0) , new Integer(), 'Maximum file size allowed in bytes. Maximum allowed value is ' . App::getEnv('_APP_STORAGE_LIMIT', 0) . '. For self-hosted setups you can change the max limit by changing the `_APP_STORAGE_LIMIT` environment variable. [Learn more about storage environment variables](docs/environment-variables#storage)', true)
2021-06-18 19:34:52 +12:00
->param('allowedFileExtensions', [], new ArrayList(new Text(64)), 'Allowed file extensions', true)
->param('enabled', true, new Boolean(), 'Is bucket enabled?', true)
->param('adapter', 'local', new WhiteList(['local']), 'Storage adapter.', true)
2021-07-15 22:35:08 +12:00
->param('encryption', true, new Boolean(), 'Is encryption enabled? For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER) . ' encryption is skipped even if it\'s enabled', true)
->param('antiVirus', true, new Boolean(), 'Is virus scanning enabled? For file size above ' . Storage::human(APP_LIMIT_ANTIVIRUS) . ' AntiVirus scanning is skipped even if it\'s enabled', true)
2021-06-14 23:32:09 +12:00
->inject('response')
2021-06-15 20:34:49 +12:00
->inject('dbForInternal')
2021-06-14 23:32:09 +12:00
->inject('audits')
2021-09-10 20:49:16 +12:00
->inject('usage')
->action(function ($bucketId, $name, $permission, $read, $write, $maximumFileSize, $allowedFileExtensions, $enabled, $adapter, $encryption, $antiVirus, $response, $dbForInternal, $audits, $usage) {
2021-06-14 23:59:47 +12:00
/** @var Appwrite\Utopia\Response $response */
2021-06-15 20:34:49 +12:00
/** @var Utopia\Database\Database $dbForInternal */
2021-06-14 23:59:47 +12:00
/** @var Appwrite\Event\Event $audits */
2021-09-10 20:49:16 +12:00
/** @var Appwrite\Stats\Stats $usage */
2021-06-14 23:59:47 +12:00
2021-09-02 22:49:07 +12:00
$bucketId = $bucketId == 'unique()' ? $dbForInternal->getId() : $bucketId;
try {
$dbForInternal->createCollection('bucket_' . $bucketId, [
2021-09-02 23:06:54 +12:00
new Document([
2021-09-02 22:49:07 +12:00
'$id' => 'dateCreated',
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => 0,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
2021-09-02 23:06:54 +12:00
]),
new Document([
2021-09-02 22:49:07 +12:00
'array' => false,
'$id' => 'bucketId',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
2021-09-02 23:06:54 +12:00
]),
new Document([
2021-09-02 22:49:07 +12:00
'$id' => 'name',
'type' => Database::VAR_STRING,
'format' => '',
'size' => 2048,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
2021-09-02 23:06:54 +12:00
]),
new Document([
2021-09-02 22:49:07 +12:00
'$id' => 'path',
'type' => Database::VAR_STRING,
'format' => '',
'size' => 2048,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
2021-09-02 23:06:54 +12:00
]),
new Document([
2021-09-02 22:49:07 +12:00
'$id' => 'signature',
'type' => Database::VAR_STRING,
'format' => '',
'size' => 2048,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
2021-09-02 23:06:54 +12:00
]),
new Document([
2021-09-02 22:49:07 +12:00
'$id' => 'mimeType',
'type' => Database::VAR_STRING,
'format' => '',
'size' => 127, // https://tools.ietf.org/html/rfc4288#section-4.2
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
2021-09-02 23:06:54 +12:00
]),
new Document([
2021-09-02 22:49:07 +12:00
'$id' => 'sizeOriginal',
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => 0,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
2021-09-02 23:06:54 +12:00
]),
new Document([
2021-09-02 22:49:07 +12:00
'$id' => 'sizeActual',
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => 0,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
2021-09-02 23:06:54 +12:00
]),
new Document([
2021-09-02 22:49:07 +12:00
'$id' => 'algorithm',
'type' => Database::VAR_STRING,
'format' => '',
'size' => 255,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
2021-09-02 23:06:54 +12:00
]),
new Document([
2021-09-02 22:49:07 +12:00
'$id' => 'comment',
'type' => Database::VAR_STRING,
'format' => '',
'size' => 2048,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
2021-09-02 23:06:54 +12:00
]),
new Document([
2021-09-02 22:49:07 +12:00
'$id' => 'openSSLVersion',
'type' => Database::VAR_STRING,
'format' => '',
'size' => 64,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
2021-09-02 23:06:54 +12:00
]),
new Document([
2021-09-02 22:49:07 +12:00
'$id' => 'openSSLCipher',
'type' => Database::VAR_STRING,
'format' => '',
'size' => 64,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
2021-09-02 23:06:54 +12:00
]),
new Document([
2021-09-02 22:49:07 +12:00
'$id' => 'openSSLTag',
'type' => Database::VAR_STRING,
'format' => '',
'size' => 2048,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
2021-09-02 23:06:54 +12:00
]),
new Document([
2021-09-02 22:49:07 +12:00
'$id' => 'openSSLIV',
'type' => Database::VAR_STRING,
'format' => '',
'size' => 2048,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
2021-09-02 23:06:54 +12:00
]),
new Document([
'$id' => 'chunksTotal',
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => 0,
'signed' => false,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
]),
new Document([
'$id' => 'chunksUploaded',
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => 0,
'signed' => false,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
]),
2021-09-02 22:49:07 +12:00
], [
2021-09-02 23:06:54 +12:00
new Document([
2021-09-02 22:49:07 +12:00
'$id' => '_key_bucket',
'type' => Database::INDEX_KEY,
'attributes' => ['bucketId'],
'lengths' => [Database::LENGTH_KEY],
'orders' => [Database::ORDER_ASC],
2021-09-02 23:06:54 +12:00
]),
new Document([
2021-09-02 22:49:07 +12:00
'$id' => '_fulltext_name',
'type' => Database::INDEX_FULLTEXT,
'attributes' => ['name'],
'lengths' => [1024],
'orders' => [Database::ORDER_ASC],
2021-09-02 23:06:54 +12:00
]),
2021-09-02 22:49:07 +12:00
]);
2021-09-02 23:06:54 +12:00
$bucket = $dbForInternal->createDocument('buckets', new Document([
2021-09-02 22:49:07 +12:00
'$id' => $bucketId,
'$collection' => 'buckets',
'dateCreated' => \time(),
'dateUpdated' => \time(),
'name' => $name,
'permission' => $permission,
2021-09-02 22:49:07 +12:00
'maximumFileSize' => $maximumFileSize,
'allowedFileExtensions' => $allowedFileExtensions,
'enabled' => $enabled,
'adapter' => $adapter,
'encryption' => $encryption,
'antiVirus' => $antiVirus,
'$read' => $read,
'$write' => $write,
]));
} catch (Duplicate $th) {
throw new Exception('Bucket already exists', 409);
}
2021-06-14 23:59:47 +12:00
$audits
->setParam('event', 'storage.buckets.create')
2021-09-02 23:06:54 +12:00
->setParam('resource', 'storage/buckets/' . $bucket->getId())
->setParam('data', $bucket->getArrayCopy())
2021-06-14 23:59:47 +12:00
;
2021-09-10 20:49:16 +12:00
$usage->setParam('storage.buckets.create', 1);
2021-06-15 20:34:49 +12:00
$response->setStatusCode(Response::STATUS_CODE_CREATED);
2021-09-02 23:06:54 +12:00
$response->dynamic($bucket, Response::MODEL_BUCKET);
2021-06-14 23:32:09 +12:00
});
2021-06-15 19:23:22 +12:00
App::get('/v1/storage/buckets')
->desc('List buckets')
->groups(['api', 'storage'])
->label('scope', 'buckets.read')
2021-06-16 17:43:59 +12:00
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
2021-06-15 19:23:22 +12:00
->label('sdk.namespace', 'storage')
->label('sdk.method', 'listBuckets')
->label('sdk.description', '/docs/references/storage/list-buckets.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_BUCKET_LIST)
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
->param('limit', 25, new Range(0, 100), 'Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true)
->param('offset', 0, new Range(0, 2000), 'Results offset. The default value is 0. Use this param to manage pagination.', true)
2021-09-02 20:30:10 +12:00
->param('after', '', new UID(), 'ID of the bucket used as the starting point for the query, excluding the bucket itself. Should be used for efficient pagination when working with large sets of data.', true)
2021-06-15 19:23:22 +12:00
->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true)
->inject('response')
2021-06-15 20:37:23 +12:00
->inject('dbForInternal')
2021-09-10 20:49:16 +12:00
->inject('usage')
->action(function ($search, $limit, $offset, $after, $orderType, $response, $dbForInternal, $usage) {
2021-06-15 19:23:22 +12:00
/** @var Appwrite\Utopia\Response $response */
2021-06-15 20:37:23 +12:00
/** @var Utopia\Database\Database $dbForInternal */
2021-09-10 20:49:16 +12:00
/** @var Appwrite\Stats\Stats $usage */
2021-06-15 19:23:22 +12:00
2021-06-15 20:37:23 +12:00
$queries = ($search) ? [new Query('name', Query::TYPE_SEARCH, $search)] : [];
2021-09-02 20:30:10 +12:00
if (!empty($after)) {
$afterBucket = $dbForInternal->getDocument('buckets', $after);
if ($afterBucket->isEmpty()) {
throw new Exception("Bucket '{$after}' for the 'after' value not found.", 400);
}
}
2021-06-15 19:23:22 +12:00
2021-09-10 20:49:16 +12:00
$usage->setParam('storage.buckets.read', 1);
$response->dynamic(new Document([
2021-09-02 22:11:10 +12:00
'buckets' => $dbForInternal->find('buckets', $queries, $limit, $offset, [], [$orderType], $afterBucket ?? null),
2021-06-15 20:37:23 +12:00
'sum' => $dbForInternal->count('buckets', $queries, APP_LIMIT_COUNT),
2021-06-15 19:23:22 +12:00
]), Response::MODEL_BUCKET_LIST);
});
2021-06-15 19:48:59 +12:00
App::get('/v1/storage/buckets/:bucketId')
->desc('Get Bucket')
->groups(['api', 'storage'])
->label('scope', 'buckets.read')
2021-06-16 17:43:59 +12:00
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
2021-06-15 19:48:59 +12:00
->label('sdk.namespace', 'storage')
->label('sdk.method', 'getBucket')
->label('sdk.description', '/docs/references/storage/get-bucket.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_BUCKET)
->param('bucketId', '', new UID(), 'Bucket unique ID.')
->inject('response')
2021-06-15 20:39:36 +12:00
->inject('dbForInternal')
2021-09-10 20:49:16 +12:00
->inject('usage')
->action(function ($bucketId, $response, $dbForInternal, $usage) {
2021-06-15 19:48:59 +12:00
/** @var Appwrite\Utopia\Response $response */
2021-06-15 20:39:36 +12:00
/** @var Utopia\Database\Database $dbForInternal */
2021-09-10 20:49:16 +12:00
/** @var Appwrite\Stats\Stats $usage */
2021-06-15 19:48:59 +12:00
2021-06-15 20:39:36 +12:00
$bucket = $dbForInternal->getDocument('buckets', $bucketId);
2021-06-15 19:48:59 +12:00
if ($bucket->isEmpty()) {
2021-06-15 19:48:59 +12:00
throw new Exception('Bucket not found', 404);
}
2021-09-10 20:49:16 +12:00
$usage->setParam('storage.buckets.read', 1);
$response->dynamic($bucket, Response::MODEL_BUCKET);
2021-06-15 19:48:59 +12:00
});
2021-06-16 21:59:47 +12:00
App::put('/v1/storage/buckets/:bucketId')
2021-06-16 22:03:44 +12:00
->desc('Update Bucket')
2021-06-16 21:59:47 +12:00
->groups(['api', 'storage'])
2021-06-16 22:03:44 +12:00
->label('scope', 'buckets.write')
2021-06-17 19:49:49 +12:00
->label('event', 'storage.buckets.update')
2021-06-16 21:59:47 +12:00
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'storage')
2021-06-16 22:03:44 +12:00
->label('sdk.method', 'updateBucket')
->label('sdk.description', '/docs/references/storage/update-bucket.md')
2021-06-16 21:59:47 +12:00
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_BUCKET)
->param('bucketId', '', new UID(), 'Bucket unique ID.')
->param('name', null, new Text(128), 'Bucket name', false)
2021-09-30 19:23:19 +13:00
->param('read', null, new Permissions(), 'An array of strings with read permissions. By default inherits the existing read permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.', true)
->param('write', null, new Permissions(), 'An array of strings with write permissions. By default inherits the existing write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.', true)
2021-06-18 19:48:57 +12:00
->param('maximumFileSize', null, new Integer(), 'Maximum file size allowed in bytes. Maximum allowed value is ' . App::getEnv('_APP_STORAGE_LIMIT', 0) . '. For self hosted version you can change the limit by changing _APP_STORAGE_LIMIT environment variable. [Learn more about storage environment variables](docs/environment-variables#storage)', true)
2021-06-18 19:34:52 +12:00
->param('allowedFileExtensions', [], new ArrayList(new Text(64)), 'Allowed file extensions', true)
2021-06-16 21:59:47 +12:00
->param('enabled', true, new Boolean(), 'Is bucket enabled?', true)
2021-07-15 22:35:08 +12:00
->param('encryption', true, new Boolean(), 'Is encryption enabled? For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER) . ' encryption is skipped even if it\'s enabled', true)
->param('antiVirus', true, new Boolean(), 'Is virus scanning enabled? For file size above ' . Storage::human(APP_LIMIT_ANTIVIRUS) . ' AntiVirus scanning is skipped even if it\'s enabled', true)
2021-06-16 21:59:47 +12:00
->inject('response')
->inject('dbForInternal')
->inject('audits')
2021-09-10 20:49:16 +12:00
->inject('usage')
->action(function ($bucketId, $name, $read, $write, $maximumFileSize, $allowedFileExtensions, $enabled, $encryption, $antiVirus, $response, $dbForInternal, $audits, $usage) {
2021-06-16 21:59:47 +12:00
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Appwrite\Event\Event $audits */
2021-09-10 20:49:16 +12:00
/** @var Appwrite\Stats\Stats $usage */
2021-06-16 21:59:47 +12:00
$bucket = $dbForInternal->getDocument('buckets', $bucketId);
if ($bucket->isEmpty()) {
2021-06-16 21:59:47 +12:00
throw new Exception('Bucket not found', 404);
}
$read ??= $bucket->getAttribute('$read', []); // By default inherit read permissions
$write ??= $bucket->getAttribute('$write', []); // By default inherit write permissions
$read ??= $bucket->getAttribute('$read', []); // By default inherit read permissions
$write ??= $bucket->getAttribute('$write',[]); // By default inherit write permissions
$read ??= $bucket->getAttribute('$read', []); // By default inherit read permissions
$write ??= $bucket->getAttribute('$write', []); // By default inherit write permissions
$maximumFileSize ??= $bucket->getAttribute('maximumFileSize', (int)App::getEnv('_APP_STORAGE_LIMIT', 0));
$allowedFileExtensions ??= $bucket->getAttribute('allowedFileExtensions', []);
$enabled ??= $bucket->getAttribute('enabled', true);
$encryption ??= $bucket->getAttribute('encryption', true);
$antiVirus ??= $bucket->getAttribute('antiVirus', true);
2021-06-16 21:59:47 +12:00
$bucket = $dbForInternal->updateDocument('buckets', $bucket->getId(), $bucket
2021-07-07 22:07:11 +12:00
->setAttribute('name', $name)
->setAttribute('$read', $read)
->setAttribute('$write', $write)
->setAttribute('maximumFileSize', $maximumFileSize)
->setAttribute('allowedFileExtensions', $allowedFileExtensions)
->setAttribute('enabled', $enabled)
->setAttribute('encryption', $encryption)
->setAttribute('antiVirus', $antiVirus)
2021-06-16 21:59:47 +12:00
);
$audits
->setParam('event', 'storage.buckets.update')
->setParam('resource', 'storage/buckets/' . $bucket->getId())
->setParam('data', $bucket->getArrayCopy())
;
2021-09-10 20:49:16 +12:00
$usage->setParam('storage.buckets.update', 1);
$response->dynamic($bucket, Response::MODEL_BUCKET);
2021-06-16 21:59:47 +12:00
});
2021-06-16 22:17:14 +12:00
App::delete('/v1/storage/buckets/:bucketId')
->desc('Delete Bucket')
->groups(['api', 'storage'])
->label('scope', 'buckets.write')
2021-06-17 19:49:49 +12:00
->label('event', 'storage.buckets.delete')
2021-06-16 22:17:14 +12:00
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'storage')
->label('sdk.method', 'deleteBucket')
->label('sdk.description', '/docs/references/storage/delete-bucket.md')
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
->label('sdk.response.model', Response::MODEL_NONE)
->param('bucketId', '', new UID(), 'Bucket unique ID.')
->inject('response')
->inject('dbForInternal')
->inject('audits')
->inject('deletes')
2021-06-17 20:23:21 +12:00
->inject('events')
2021-09-10 20:49:16 +12:00
->inject('usage')
->action(function ($bucketId, $response, $dbForInternal, $audits, $deletes, $events, $usage) {
2021-06-16 22:17:14 +12:00
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Appwrite\Event\Event $audits */
/** @var Appwrite\Event\Event $deletes */
2021-06-17 20:23:21 +12:00
/** @var Appwrite\Event\Event $events */
2021-09-10 20:49:16 +12:00
/** @var Appwrite\Stats\Stats $usage */
2021-06-16 22:17:14 +12:00
$bucket = $dbForInternal->getDocument('buckets', $bucketId);
if ($bucket->isEmpty()) {
2021-06-16 22:17:14 +12:00
throw new Exception('Bucket not found', 404);
}
2021-09-15 16:54:49 +12:00
if(!$dbForInternal->deleteDocument('buckets', $bucketId)) {
throw new Exception('Failed to remove project from DB', 500);
}
2021-06-16 22:17:14 +12:00
$deletes
->setParam('type', DELETE_TYPE_DOCUMENT)
->setParam('document', $bucket)
;
2021-06-17 20:23:21 +12:00
$events
->setParam('eventData', $response->output($bucket, Response::MODEL_BUCKET))
2021-06-17 20:23:21 +12:00
;
2021-06-16 22:17:14 +12:00
$audits
->setParam('event', 'storage.buckets.delete')
->setParam('resource', 'storage/buckets/' . $bucket->getId())
->setParam('data', $bucket->getArrayCopy())
;
2021-09-10 20:49:16 +12:00
$usage->setParam('storage.buckets.delete', 1);
2021-06-16 22:17:14 +12:00
$response->noContent();
});
2021-06-17 22:10:58 +12:00
App::post('/v1/storage/buckets/:bucketId/files')
2021-07-07 22:07:11 +12:00
->alias('/v1/storage/files', ['bucketId' => 'default'])
2021-06-17 22:10:58 +12:00
->desc('Create File')
->groups(['api', 'storage'])
->label('scope', 'files.write')
->label('event', 'storage.files.create')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'storage')
->label('sdk.method', 'createFile')
->label('sdk.description', '/docs/references/storage/create-file.md')
->label('sdk.request.type', 'multipart/form-data')
->label('sdk.methodType', 'upload')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_FILE)
->param('bucketId', null, new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).')
->param('fileId', '', new CustomId(), 'Unique Id. Choose your own unique ID or pass the string `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.')
2021-06-17 22:10:58 +12:00
->param('file', [], new File(), 'Binary file.', false)
2021-09-30 19:23:19 +13:00
->param('read', null, new Permissions(), 'An array of strings with read permissions. By default only the current user is granted with read permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.', true)
->param('write', null, new Permissions(), 'An array of strings with write permissions. By default only the current user is granted with write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.', true)
2021-06-17 22:10:58 +12:00
->inject('request')
->inject('response')
->inject('dbForInternal')
->inject('user')
->inject('audits')
->inject('usage')
->action(function ($bucketId, $fileId, $file, $read, $write, $request, $response, $dbForInternal, $user, $audits, $usage) {
2021-06-17 22:10:58 +12:00
/** @var Utopia\Swoole\Request $request */
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
2021-07-26 02:47:18 +12:00
/** @var Utopia\Database\Document $user */
2021-06-17 22:10:58 +12:00
/** @var Appwrite\Event\Event $audits */
2021-08-16 19:25:20 +12:00
/** @var Appwrite\Stats\Stats $usage */
2021-06-17 22:10:58 +12:00
$bucket = $dbForInternal->getDocument('buckets', $bucketId);
2021-06-17 22:10:58 +12:00
2021-07-07 22:07:11 +12:00
if ($bucket->isEmpty()) {
throw new Exception('Bucket not found', 404);
2021-06-17 22:10:58 +12:00
}
$file = $request->getFiles('file');
2021-07-08 23:26:11 +12:00
/**
* Validators
*/
2021-06-23 00:12:46 +12:00
$allowedFileExtensions = $bucket->getAttribute('allowedFileExtensions', []);
$fileExt = new FileExt($allowedFileExtensions);
$maximumFileSize = $bucket->getAttribute('maximumFileSize', 0);
2021-07-07 22:07:11 +12:00
if ($maximumFileSize > (int) App::getEnv('_APP_STORAGE_LIMIT', 0)) {
2021-06-28 17:52:45 +12:00
throw new Exception('Error bucket maximum file size is larger than _APP_STORAGE_LIMIT', 500);
}
$fileSize = new FileSize($maximumFileSize);
2021-06-17 22:10:58 +12:00
$upload = new Upload();
if (empty($file)) {
throw new Exception('No file sent', 400);
}
// Make sure we handle a single file and multiple files the same way
2021-07-07 22:07:11 +12:00
$fileName = (\is_array($file['name']) && isset($file['name'][0])) ? $file['name'][0] : $file['name'];
$fileTmpName = (\is_array($file['tmp_name']) && isset($file['tmp_name'][0])) ? $file['tmp_name'][0] : $file['tmp_name'];
$size = (\is_array($file['size']) && isset($file['size'][0])) ? $file['size'][0] : $file['size'];
$contentRange = $request->getHeader('content-range');
$fileId = $fileId == 'unique()' ? $dbForInternal->getId() : $fileId;
2021-07-07 22:07:11 +12:00
$chunk = 1;
$chunks = 1;
if (!empty($contentRange)) {
2021-07-15 21:40:41 +12:00
$start = $request->getContentRangeStart();
$end = $request->getContentRangeEnd();
$size = $request->getContentRangeSize();
2021-09-27 19:04:17 +13:00
$fileId = $request->getHeader('x-appwrite-id', $fileId);
2021-07-15 23:34:05 +12:00
if(is_null($start) || is_null($end) || is_null($size)) {
2021-07-07 22:07:11 +12:00
throw new Exception('Invalid content-range header', 400);
}
if ($end == $size) {
2021-07-13 19:58:16 +12:00
//if it's a last chunks the chunk size might differ, so we set the $chunks and $chunk to notify it's last chunk
2021-07-07 22:07:11 +12:00
$chunks = $chunk = -1;
} else {
2021-07-13 19:58:16 +12:00
// Calculate total number of chunks based on the chunk size i.e ($rangeEnd - $rangeStart)
2021-07-07 22:07:11 +12:00
$chunks = (int) ceil($size / ($end + 1 - $start));
$chunk = (int) ($start / ($end + 1 - $start));
}
}
2021-06-17 22:10:58 +12:00
// Check if file type is allowed (feature for project settings?)
2021-07-07 22:07:11 +12:00
if (!empty($allowedFileExtensions) && !$fileExt->isValid($fileName)) {
2021-06-23 00:12:46 +12:00
throw new Exception('File extension not allowed', 400);
}
2021-06-17 22:10:58 +12:00
2021-07-07 22:07:11 +12:00
if (!$fileSize->isValid($size)) { // Check if file size is exceeding allowed limit
2021-06-17 22:10:58 +12:00
throw new Exception('File size not allowed', 400);
}
$device = Storage::getDevice('files');
2021-07-07 22:07:11 +12:00
if (!$upload->isValid($fileTmpName)) {
2021-06-17 22:10:58 +12:00
throw new Exception('Invalid file', 403);
}
// Save to storage
$size ??= $device->getFileSize($fileTmpName);
2021-07-13 19:58:16 +12:00
$path = $device->getPath($fileId . '.' . \pathinfo($fileName, PATHINFO_EXTENSION));
2021-07-08 23:26:11 +12:00
$path = str_ireplace($device->getRoot(), $device->getRoot() . DIRECTORY_SEPARATOR . $bucket->getId(), $path);
2021-06-17 22:10:58 +12:00
$file = $dbForInternal->getDocument('bucket_' . $bucketId, $fileId);
2021-06-17 22:10:58 +12:00
2021-07-07 22:07:11 +12:00
if (!$file->isEmpty()) {
2021-07-15 23:34:05 +12:00
$chunks = $file->getAttribute('chunksTotal', 1);
2021-07-07 22:07:11 +12:00
if ($chunk == -1) {
$chunk = $chunks - 1;
2021-06-17 22:10:58 +12:00
}
}
2021-07-13 19:58:16 +12:00
$chunksUploaded = $device->upload($fileTmpName, $path, $chunk, $chunks);
if (empty($chunksUploaded)) {
2021-07-07 22:07:11 +12:00
throw new Exception('Failed uploading file', 500);
2021-06-17 22:10:58 +12:00
}
2021-07-13 20:16:24 +12:00
$read = (is_null($read) && !$user->isEmpty()) ? ['user:' . $user->getId()] : $read ?? [];
$write = (is_null($write) && !$user->isEmpty()) ? ['user:' . $user->getId()] : $write ?? [];
2021-07-13 19:58:16 +12:00
if ($chunksUploaded == $chunks) {
2021-07-07 22:07:11 +12:00
if (App::getEnv('_APP_STORAGE_ANTIVIRUS') === 'enabled' && $bucket->getAttribute('antiVirus', true) && $size <= APP_LIMIT_ANTIVIRUS) {
$antiVirus = new Network(App::getEnv('_APP_STORAGE_ANTIVIRUS_HOST', 'clamav'),
2021-07-13 20:16:24 +12:00
(int) App::getEnv('_APP_STORAGE_ANTIVIRUS_PORT', 3310));
2021-07-07 22:07:11 +12:00
if (!$antiVirus->fileScan($path)) {
$device->delete($path);
throw new Exception('Invalid file', 403);
}
}
2021-07-13 20:16:24 +12:00
$mimeType = $device->getFileMimeType($path); // Get mime-type before compression and encryption
$data = '';
2021-07-07 22:07:11 +12:00
// Compression
2021-07-15 22:35:08 +12:00
if ($size <= APP_STORAGE_READ_BUFFER) {
2021-07-08 23:26:11 +12:00
$data = $device->read($path);
2021-07-13 20:16:24 +12:00
$compressor = new GZIP();
$data = $compressor->compress($data);
}
2021-07-15 22:35:08 +12:00
if ($bucket->getAttribute('encryption', true) && $size <= APP_STORAGE_READ_BUFFER) {
2021-07-13 20:16:24 +12:00
if(empty($data)) {
$data = $device->read($path);
2021-07-08 23:26:11 +12:00
}
2021-07-13 20:16:24 +12:00
$key = App::getEnv('_APP_OPENSSL_KEY_V1');
$iv = OpenSSL::randomPseudoBytes(OpenSSL::cipherIVLength(OpenSSL::CIPHER_AES_128_GCM));
$data = OpenSSL::encrypt($data, OpenSSL::CIPHER_AES_128_GCM, $key, 0, $iv, $tag);
}
if(!empty($data)) {
2021-07-08 23:26:11 +12:00
if (!$device->write($path, $data, $mimeType)) {
throw new Exception('Failed to save file', 500);
}
2021-07-07 22:07:11 +12:00
}
$sizeActual = $device->getFileSize($path);
$algorithm = empty($compressor) ? '' : $compressor->getName();
$fileHash = $device->getFileHash($path);
2021-07-15 22:35:08 +12:00
if ($bucket->getAttribute('encryption', true) && $size <= APP_STORAGE_READ_BUFFER) {
2021-07-07 22:07:11 +12:00
$openSSLVersion = '1';
$openSSLCipher = OpenSSL::CIPHER_AES_128_GCM;
$openSSLTag = \bin2hex($tag);
$openSSLIV = \bin2hex($iv);
}
if ($file->isEmpty()) {
$file = $dbForInternal->createDocument('bucket_' . $bucketId, new Document([
2021-09-12 22:07:19 +12:00
'$id' => $fileId,
2021-07-07 22:07:11 +12:00
'$read' => $read,
'$write' => $write,
'dateCreated' => \time(),
'bucketId' => $bucket->getId(),
'name' => $fileName,
'path' => $path,
'signature' => $fileHash,
'mimeType' => $mimeType,
'sizeOriginal' => $size,
'sizeActual' => $sizeActual,
'algorithm' => $algorithm,
'comment' => '',
2021-07-13 19:58:16 +12:00
'chunksTotal' => $chunks,
'chunksUploaded' => $chunksUploaded,
2021-07-07 22:07:11 +12:00
'openSSLVersion' => $openSSLVersion,
'openSSLCipher' => $openSSLCipher,
'openSSLTag' => $openSSLTag,
'openSSLIV' => $openSSLIV,
2021-07-13 19:58:16 +12:00
]));
2021-07-07 22:07:11 +12:00
} else {
$file = $dbForInternal->updateDocument('bucket_' . $bucketId, $fileId, $file
2021-07-07 22:07:11 +12:00
->setAttribute('$read', $read)
->setAttribute('$write', $write)
->setAttribute('signature', $fileHash)
->setAttribute('mimeType', $mimeType)
->setAttribute('sizeActual', $sizeActual)
->setAttribute('algorithm', $algorithm)
->setAttribute('openSSLVersion', $openSSLVersion)
->setAttribute('openSSLCipher', $openSSLCipher)
->setAttribute('openSSLTag', $openSSLTag)
->setAttribute('openSSLIV', $openSSLIV)
);
}
} else {
if ($file->isEmpty()) {
$file = $dbForInternal->createDocument('bucket_' . $bucketId, new Document([
2021-07-13 19:58:16 +12:00
'$id' => $fileId,
2021-07-13 20:16:24 +12:00
'$read' => $read,
'$write' => $write,
2021-07-07 22:07:11 +12:00
'dateCreated' => \time(),
'bucketId' => $bucket->getId(),
'name' => $fileName,
'path' => $path,
'signature' => '',
'mimeType' => '',
'sizeOriginal' => $size,
'sizeActual' => 0,
'algorithm' => '',
'comment' => '',
2021-07-13 19:58:16 +12:00
'chunksTotal' => $chunks,
'chunksUploaded' => $chunksUploaded,
]));
2021-07-07 22:07:11 +12:00
} else {
$file = $dbForInternal->updateDocument('bucket_' . $bucketId, $fileId, $file
2021-07-13 19:58:16 +12:00
->setAttribute('chunksUploaded', $chunksUploaded)
2021-07-07 22:07:11 +12:00
);
}
}
2021-06-17 22:10:58 +12:00
$audits
->setParam('event', 'storage.files.create')
2021-07-07 22:07:11 +12:00
->setParam('resource', 'storage/files/' . $file->getId())
2021-06-17 22:10:58 +12:00
;
$usage
2021-09-27 19:32:41 +13:00
->setParam('storage', $sizeActual ?? 0)
2021-08-16 19:25:20 +12:00
->setParam('storage.files.create', 1)
2021-09-10 20:49:16 +12:00
->setParam('bucketId', $bucketId)
2021-06-17 22:10:58 +12:00
;
$response->setStatusCode(Response::STATUS_CODE_CREATED);
2021-07-26 02:47:18 +12:00
$response->dynamic($file, Response::MODEL_FILE);
2020-10-31 21:42:41 +13:00
;
2021-06-17 22:10:58 +12:00
});
2021-06-18 21:24:16 +12:00
App::get('/v1/storage/buckets/:bucketId/files')
->alias('/v1/storage/files', ['bucketId' => 'default'])
2021-06-18 21:24:16 +12:00
->desc('List Files')
->groups(['api', 'storage'])
->label('scope', 'files.read')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'storage')
->label('sdk.method', 'listFiles')
->label('sdk.description', '/docs/references/storage/list-files.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_FILE_LIST)
->param('bucketId', null, new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).')
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
->param('limit', 25, new Range(0, 100), 'Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true)
->param('offset', 0, new Range(0, 2000), 'Results offset. The default value is 0. Use this param to manage pagination.', true)
->param('after', '', new UID(), 'ID of the file used as the starting point for the query, excluding the file itself. Should be used for efficient pagination when working with large sets of data.', true)
2021-06-18 21:24:16 +12:00
->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true)
->inject('response')
->inject('dbForInternal')
2021-08-16 19:25:20 +12:00
->inject('usage')
->action(function ($bucketId, $search, $limit, $offset, $after, $orderType, $response, $dbForInternal, $usage) {
2021-06-18 21:24:16 +12:00
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
2021-08-16 19:25:20 +12:00
/** @var Appwrite\Stats\Stats $usage */
2021-06-18 21:24:16 +12:00
2021-06-20 22:55:24 +12:00
$bucket = $dbForInternal->getDocument('buckets', $bucketId);
2021-07-07 22:07:11 +12:00
if ($bucket->isEmpty()) {
throw new Exception('Bucket not found', 404);
2021-06-20 22:55:24 +12:00
}
2021-06-18 21:24:16 +12:00
$queries = [new Query('bucketId', Query::TYPE_EQUAL, [$bucketId])];
2021-07-07 22:07:11 +12:00
if ($search) {
2021-06-18 21:24:16 +12:00
$queries[] = [new Query('name', Query::TYPE_SEARCH, [$search])];
}
2021-08-07 00:36:27 +12:00
if (!empty($after)) {
2021-09-02 22:49:07 +12:00
$afterFile = $dbForInternal->getDocument('bucket_' . $bucketId, $after);
2021-08-07 00:36:27 +12:00
if ($afterFile->isEmpty()) {
throw new Exception("File '{$after}' for the 'after' value not found.", 400);
2021-08-07 00:36:27 +12:00
}
}
2021-08-16 19:25:20 +12:00
$usage
->setParam('storage.files.read', 1)
2021-09-10 20:49:16 +12:00
->setParam('bucketId', $bucketId)
2021-08-16 19:25:20 +12:00
;
2021-07-26 02:47:18 +12:00
$response->dynamic(new Document([
2021-09-02 22:49:07 +12:00
'files' => $dbForInternal->find('bucket_' . $bucketId, $queries, $limit, $offset, [], [$orderType], $afterFile ?? null),
'sum' => $dbForInternal->count('bucket_' . $bucketId, $queries, APP_LIMIT_COUNT),
2021-06-18 21:24:16 +12:00
]), Response::MODEL_FILE_LIST);
});
2021-06-18 21:33:00 +12:00
App::get('/v1/storage/buckets/:bucketId/files/:fileId')
->alias('/v1/storage/files/:fileId', ['bucketId' => 'default'])
2021-06-18 21:33:00 +12:00
->desc('Get File')
->groups(['api', 'storage'])
->label('scope', 'files.read')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'storage')
->label('sdk.method', 'getFile')
->label('sdk.description', '/docs/references/storage/get-file.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_FILE)
->param('bucketId', null, new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).')
->param('fileId', '', new UID(), 'File unique ID.')
->inject('response')
->inject('dbForInternal')
2021-08-16 19:25:20 +12:00
->inject('usage')
->action(function ($bucketId, $fileId, $response, $dbForInternal, $usage) {
2021-06-18 21:33:00 +12:00
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
2021-08-16 19:25:20 +12:00
/** @var Appwrite\Stats\Stats $usage */
2021-06-18 21:33:00 +12:00
$bucket = $dbForInternal->getDocument('buckets', $bucketId);
2021-06-20 22:55:24 +12:00
2021-07-07 22:07:11 +12:00
if ($bucket->isEmpty()) {
throw new Exception('Bucket not found', 404);
2021-06-20 22:55:24 +12:00
}
2021-09-02 22:49:07 +12:00
$file = $dbForInternal->getDocument('bucket_' . $bucketId, $fileId);
2021-06-18 21:33:00 +12:00
2021-07-07 22:07:11 +12:00
if ($file->isEmpty() || $file->getAttribute('bucketId') != $bucketId) {
2021-06-18 21:33:00 +12:00
throw new Exception('File not found', 404);
}
2021-08-16 19:25:20 +12:00
$usage
->setParam('storage.files.read', 1)
2021-09-10 20:49:16 +12:00
->setParam('bucketId', $bucketId)
2021-08-16 19:25:20 +12:00
;
2021-07-26 02:47:18 +12:00
$response->dynamic($file, Response::MODEL_FILE);
2021-06-18 21:33:00 +12:00
});
App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
->alias('/v1/storage/files/:fileId/preview', ['bucketId' => 'default'])
->desc('Get File Preview')
->groups(['api', 'storage'])
->label('scope', 'files.read')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'storage')
->label('sdk.method', 'getFilePreview')
->label('sdk.description', '/docs/references/storage/get-file-preview.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_IMAGE)
->label('sdk.methodType', 'location')
->param('bucketId', null, new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).')
->param('fileId', '', new UID(), 'File unique ID')
->param('width', 0, new Range(0, 4000), 'Resize preview image width, Pass an integer between 0 to 4000.', true)
->param('height', 0, new Range(0, 4000), 'Resize preview image height, Pass an integer between 0 to 4000.', true)
2021-07-05 00:37:00 +12:00
->param('gravity', Image::GRAVITY_CENTER, new WhiteList(Image::getGravityTypes()), 'Image crop gravity. Can be one of ' . implode(",", Image::getGravityTypes()), true)
->param('quality', 100, new Range(0, 100), 'Preview image quality. Pass an integer between 0 to 100. Defaults to 100.', true)
->param('borderWidth', 0, new Range(0, 100), 'Preview image border in pixels. Pass an integer between 0 to 100. Defaults to 0.', true)
->param('borderColor', '', new HexColor(), 'Preview image border color. Use a valid HEX color, no # is needed for prefix.', true)
->param('borderRadius', 0, new Range(0, 4000), 'Preview image border radius in pixels. Pass an integer between 0 to 4000.', true)
2021-07-07 22:07:11 +12:00
->param('opacity', 1, new Range(0, 1, Range::TYPE_FLOAT), 'Preview image opacity. Only works with images having an alpha channel (like png). Pass a number between 0 to 1.', true)
->param('rotation', 0, new Range(0, 360), 'Preview image rotation in degrees. Pass an integer between 0 and 360.', true)
->param('background', '', new HexColor(), 'Preview image background color. Only works with transparent images (png). Use a valid HEX color, no # is needed for prefix.', true)
->param('output', '', new WhiteList(\array_keys(Config::getParam('storage-outputs')), true), 'Output format type (jpeg, jpg, png, gif and webp).', true)
->inject('request')
->inject('response')
->inject('project')
->inject('dbForInternal')
2021-08-16 19:25:20 +12:00
->inject('usage')
->action(function ($bucketId, $fileId, $width, $height, $gravity, $quality, $borderWidth, $borderColor, $borderRadius, $opacity, $rotation, $background, $output, $request, $response, $project, $dbForInternal, $usage) {
/** @var Utopia\Swoole\Request $request */
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Document $project */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Appwrite\Stats\Stats $usage */
$storage = 'files';
if (!\extension_loaded('imagick')) {
throw new Exception('Imagick extension is missing', 500);
}
if (!Storage::exists($storage)) {
throw new Exception('No such storage device', 400);
}
2021-06-21 20:05:11 +12:00
$bucket = $dbForInternal->getDocument('buckets', $bucketId);
2021-07-07 22:07:11 +12:00
if ($bucket->isEmpty()) {
throw new Exception('Bucket not found', 404);
2021-06-21 20:05:11 +12:00
}
if ((\strpos($request->getAccept(), 'image/webp') === false) && ('webp' == $output)) { // Fallback webp to jpeg when no browser support
$output = 'jpg';
}
$inputs = Config::getParam('storage-inputs');
$outputs = Config::getParam('storage-outputs');
$fileLogos = Config::getParam('storage-logos');
2021-07-07 22:07:11 +12:00
$date = \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT'; // 45 days cache
$key = \md5($fileId . $width . $height . $quality . $borderWidth . $borderColor . $borderRadius . $opacity . $rotation . $background . $storage . $output);
2021-09-02 22:49:07 +12:00
$file = $dbForInternal->getDocument('bucket_' . $bucketId, $fileId);
2021-06-20 23:25:48 +12:00
if ($file->isEmpty() || $file->getAttribute('bucketId') != $bucketId) {
throw new Exception('File not found', 404);
}
$path = $file->getAttribute('path');
$type = \strtolower(\pathinfo($path, PATHINFO_EXTENSION));
$algorithm = $file->getAttribute('algorithm');
$cipher = $file->getAttribute('openSSLCipher');
$mime = $file->getAttribute('mimeType');
if (!\in_array($mime, $inputs)) {
$path = (\array_key_exists($mime, $fileLogos)) ? $fileLogos[$mime] : $fileLogos['default'];
$algorithm = null;
$cipher = null;
$background = (empty($background)) ? 'eceff1' : $background;
$type = \strtolower(\pathinfo($path, PATHINFO_EXTENSION));
2021-07-07 22:07:11 +12:00
$key = \md5($path . $width . $height . $quality . $borderWidth . $borderColor . $borderRadius . $opacity . $rotation . $background . $storage . $output);
}
$compressor = new GZIP();
$device = Storage::getDevice('files');
if (!\file_exists($path)) {
throw new Exception('File not found', 404);
}
2021-07-07 22:07:11 +12:00
$cache = new Cache(new Filesystem(APP_STORAGE_CACHE . '/app-' . $project->getId())); // Limit file number or size
$data = $cache->load($key, 60 * 60 * 24 * 30 * 3/* 3 months */);
if ($data) {
$output = (empty($output)) ? $type : $output;
return $response
->setContentType((\array_key_exists($output, $outputs)) ? $outputs[$output] : $outputs['jpg'])
->addHeader('Expires', $date)
->addHeader('X-Appwrite-Cache', 'hit')
->send($data)
;
}
$source = $device->read($path);
if (!empty($cipher)) { // Decrypt
$source = OpenSSL::decrypt(
$source,
$file->getAttribute('openSSLCipher'),
2021-07-07 22:07:11 +12:00
App::getEnv('_APP_OPENSSL_KEY_V' . $file->getAttribute('openSSLVersion')),
0,
\hex2bin($file->getAttribute('openSSLIV')),
\hex2bin($file->getAttribute('openSSLTag'))
);
}
if (!empty($algorithm)) {
$source = $compressor->decompress($source);
}
$image = new Image($source);
$image->crop((int) $width, (int) $height, $gravity);
2021-07-07 22:07:11 +12:00
if (!empty($opacity) || $opacity == 0) {
$image->setOpacity($opacity);
}
if (!empty($background)) {
2021-07-07 22:07:11 +12:00
$image->setBackground('#' . $background);
}
2021-07-07 22:07:11 +12:00
if (!empty($borderWidth)) {
$image->setBorder($borderWidth, '#' . $borderColor);
}
if (!empty($borderRadius)) {
$image->setBorderRadius($borderRadius);
}
if (!empty($rotation)) {
$image->setRotation($rotation);
}
$output = (empty($output)) ? $type : $output;
$data = $image->output($output, $quality);
$cache->save($key, $data);
2021-08-16 19:25:20 +12:00
$usage
->setParam('storage.files.read', 1)
2021-09-10 20:49:16 +12:00
->setParam('bucketId', $bucketId)
2021-08-16 19:25:20 +12:00
;
$response
->setContentType($outputs[$output])
->addHeader('Expires', $date)
->addHeader('X-Appwrite-Cache', 'miss')
->send($data)
;
unset($image);
});
App::get('/v1/storage/buckets/:bucketId/files/:fileId/download')
->alias('/v1/storage/files/:fileId/download', ['bucketId' => 'default'])
->desc('Get File for Download')
->groups(['api', 'storage'])
->label('scope', 'files.read')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'storage')
->label('sdk.method', 'getFileDownload')
->label('sdk.description', '/docs/references/storage/get-file-download.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', '*/*')
->label('sdk.methodType', 'location')
->param('bucketId', null, new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).')
->param('fileId', '', new UID(), 'File unique ID.')
2021-09-12 19:03:25 +12:00
->inject('request')
->inject('response')
->inject('dbForInternal')
2021-08-16 19:25:20 +12:00
->inject('usage')
2021-09-12 19:03:25 +12:00
->action(function ($bucketId, $fileId, $request, $response, $dbForInternal, $usage) {
/** @var Utopia\Swoole\Request $request */
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
2021-08-16 19:25:20 +12:00
/** @var Appwrite\Stats\Stats $usage */
$bucket = $dbForInternal->getDocument('buckets', $bucketId);
2021-07-07 22:07:11 +12:00
if ($bucket->isEmpty()) {
throw new Exception('Bucket not found', 404);
}
2021-09-02 22:49:07 +12:00
$file = $dbForInternal->getDocument('bucket_' . $bucketId, $fileId);
2021-06-20 23:25:48 +12:00
if ($file->isEmpty() || $file->getAttribute('bucketId') != $bucketId) {
throw new Exception('File not found', 404);
}
$path = $file->getAttribute('path', '');
2021-07-12 22:23:01 +12:00
$device = Storage::getDevice('files');
if (!$device->exists($path)) {
2021-07-07 22:07:11 +12:00
throw new Exception('File not found in ' . $path, 404);
}
2021-09-12 19:03:25 +12:00
$usage
->setParam('storage.files.read', 1)
->setParam('bucketId', $bucketId)
;
2021-07-13 19:49:19 +12:00
$response
->setContentType($file->getAttribute('mimeType'))
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache
->addHeader('X-Peak', \memory_get_peak_usage())
->addHeader('Content-Disposition', 'attachment; filename="' . $file->getAttribute('name', '') . '"')
;
2021-09-13 18:17:45 +12:00
$size = $file->getAttribute('sizeOriginal', 0);
2021-09-12 19:03:25 +12:00
2021-09-12 20:50:43 +12:00
$rangeHeader = $request->getHeader('range');
2021-09-30 22:46:45 +13:00
if(!empty($rangeHeader)) {
$start = $request->getRangeStart();
$end = $request->getRangeEnd();
$unit = $request->getRangeUnit();
if($end == null) {
$end = min(($start + 2000000-1), ($size - 1));
}
if($unit != 'bytes' || $start >= $end || $end >= $size) {
2021-09-13 18:17:45 +12:00
throw new Exception('Invalid range', 416);
2021-09-12 19:03:25 +12:00
}
2021-09-30 22:46:45 +13:00
$response
->addHeader('Accept-Ranges', 'bytes')
->addHeader('Content-Range', 'bytes ' . $start . '-' . $end . '/' . $size)
->addHeader('Content-Length', $end - $start + 1)
->setStatusCode(Response::STATUS_CODE_PARTIALCONTENT);
2021-09-12 19:03:25 +12:00
}
2021-07-15 22:35:08 +12:00
$source = '';
2021-07-13 19:49:19 +12:00
if (!empty($file->getAttribute('openSSLCipher'))) { // Decrypt
2021-07-15 22:35:08 +12:00
$source = $device->read($path);
2021-07-13 19:49:19 +12:00
$source = OpenSSL::decrypt(
$source,
$file->getAttribute('openSSLCipher'),
App::getEnv('_APP_OPENSSL_KEY_V' . $file->getAttribute('openSSLVersion')),
0,
\hex2bin($file->getAttribute('openSSLIV')),
\hex2bin($file->getAttribute('openSSLTag'))
);
}
2021-07-15 22:35:08 +12:00
2021-07-13 19:49:19 +12:00
if (!empty($file->getAttribute('algorithm', ''))) {
2021-07-15 22:35:08 +12:00
if(empty($source)) {
$source = $device->read($path);
}
2021-07-13 19:49:19 +12:00
$compressor = new GZIP();
$source = $compressor->decompress($source);
2021-06-22 20:36:44 +12:00
}
2021-07-13 19:49:19 +12:00
2021-07-15 22:35:08 +12:00
if(!empty($source)) {
2021-09-12 20:50:43 +12:00
if(!empty($rangeHeader)) {
2021-09-30 22:46:45 +13:00
$response->send(substr($source, $start, ($end - $start + 1)));
2021-09-12 20:50:43 +12:00
}
2021-07-15 22:35:08 +12:00
$response->send($source);
}
2021-09-12 20:50:43 +12:00
if(!empty($rangeHeader)) {
2021-09-30 22:46:45 +13:00
$response->send($device->read($path, $start, ($end - $start + 1)));
2021-09-12 20:50:43 +12:00
}
2021-07-15 22:35:08 +12:00
if ($size > APP_STORAGE_READ_BUFFER) {
$response->addHeader('Content-Length', $device->getFileSize($path));
2021-09-30 21:41:29 +13:00
for ($i=0; $i < ceil($size / MAX_OUTPUT_CHUNK_SIZE); $i++) {
$response->chunk($device->read($path, ($i * MAX_OUTPUT_CHUNK_SIZE), min(MAX_OUTPUT_CHUNK_SIZE, $size - ($i * MAX_OUTPUT_CHUNK_SIZE))), (($i + 1) * MAX_OUTPUT_CHUNK_SIZE) >= $size);
2021-07-15 22:35:08 +12:00
}
} else {
$response->send($device->read($path));
}
});
App::get('/v1/storage/buckets/:bucketId/files/:fileId/view')
->alias('/v1/storage/files/:fileId/view', ['bucketId' => 'default'])
->desc('Get File for View')
->groups(['api', 'storage'])
->label('scope', 'files.read')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'storage')
->label('sdk.method', 'getFileView')
->label('sdk.description', '/docs/references/storage/get-file-view.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', '*/*')
->label('sdk.methodType', 'location')
->param('bucketId', null, new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).')
->param('fileId', '', new UID(), 'File unique ID.')
->inject('response')
->inject('request')
->inject('dbForInternal')
2021-08-16 19:25:20 +12:00
->inject('usage')
->action(function ($bucketId, $fileId, $response, $request, $dbForInternal, $usage) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Swoole\Request $request */
/** @var Utopia\Database\Database $dbForInternal */
2021-08-16 19:25:20 +12:00
/** @var Appwrite\Stats\Stats $usage */
$bucket = $dbForInternal->getDocument('buckets', $bucketId);
2021-07-07 22:07:11 +12:00
if ($bucket->isEmpty()) {
throw new Exception('Bucket not found', 404);
}
2021-09-02 22:49:07 +12:00
$file = $dbForInternal->getDocument('bucket_' . $bucketId, $fileId);
$mimes = Config::getParam('storage-mimes');
2021-06-20 23:25:48 +12:00
if ($file->isEmpty() || $file->getAttribute('bucketId') != $bucketId) {
throw new Exception('File not found', 404);
}
$path = $file->getAttribute('path', '');
if (!\file_exists($path)) {
2021-07-07 22:07:11 +12:00
throw new Exception('File not found in ' . $path, 404);
}
$compressor = new GZIP();
$device = Storage::getDevice('files');
$contentType = 'text/plain';
if (\in_array($file->getAttribute('mimeType'), $mimes)) {
$contentType = $file->getAttribute('mimeType');
}
2021-07-13 19:49:19 +12:00
$response
->setContentType($contentType)
->addHeader('Content-Security-Policy', 'script-src none;')
->addHeader('X-Content-Type-Options', 'nosniff')
->addHeader('Content-Disposition', 'inline; filename="' . $file->getAttribute('name', '') . '"')
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache
->addHeader('X-Peak', \memory_get_peak_usage())
;
$size = $file->getAttribute('sizeOriginal', 0);
$rangeHeader = $request->getHeader('range');
if(!empty($rangeHeader)) {
list($unit, $range) = explode('=', $rangeHeader);
if($unit == 'bytes' && !empty($range)) {
list($rangeStart, $rangeEnd) = explode('-', $range);
if(strlen($rangeStart) == 0 || strstr($range, '-') === false) {
throw new Exception('Invalid range', 416);
}
$rangeStart = (int) $rangeStart;
if(strlen($rangeEnd) == 0) {
$rangeEnd = min(($rangeStart + 2000000-1), ($size - 1));
} else {
$rangeEnd = (int) $rangeEnd;
}
if(($rangeStart >= $rangeEnd) || $rangeEnd >= $size) {
throw new Exception('Invalid range', 416);
}
$response
->addHeader('Accept-Ranges', 'bytes')
->addHeader('Content-Range', 'bytes ' . $rangeStart . '-' . $rangeEnd . '/' . $size)
->addHeader('Content-Length', $rangeEnd - $rangeStart + 1)
->setStatusCode(Response::STATUS_CODE_PARTIALCONTENT);
} else {
throw new Exception('Invalid range', 416);
}
}
2021-07-15 22:35:08 +12:00
$source = '';
2021-07-13 19:49:19 +12:00
if (!empty($file->getAttribute('openSSLCipher'))) { // Decrypt
2021-07-15 22:35:08 +12:00
$source = $device->read($path);
2021-07-13 19:49:19 +12:00
$source = OpenSSL::decrypt(
$source,
$file->getAttribute('openSSLCipher'),
App::getEnv('_APP_OPENSSL_KEY_V' . $file->getAttribute('openSSLVersion')),
0,
\hex2bin($file->getAttribute('openSSLIV')),
\hex2bin($file->getAttribute('openSSLTag'))
);
2021-07-12 22:23:01 +12:00
}
2021-07-13 19:49:19 +12:00
2021-07-15 22:35:08 +12:00
if (!empty($file->getAttribute('algorithm', ''))) {
if(empty($source)) {
$source = $device->read($path);
}
$compressor = new GZIP();
$source = $compressor->decompress($source);
}
2021-07-13 19:49:19 +12:00
2021-08-16 19:25:20 +12:00
$usage
->setParam('storage.files.read', 1)
2021-09-10 20:49:16 +12:00
->setParam('bucketId', $bucketId)
2021-08-16 19:25:20 +12:00
;
2021-07-15 22:35:08 +12:00
if(!empty($source)) {
if(!empty($rangeHeader)) {
$response->send(substr($source, $rangeStart, ($rangeEnd - $rangeStart + 1)));
}
2021-07-15 22:35:08 +12:00
$response->send($source);
}
if(!empty($rangeHeader)) {
$response->send($device->read($path, $rangeStart, ($rangeEnd - $rangeStart + 1)));
}
2021-07-15 22:35:08 +12:00
$size = $device->getFileSize($path);
if ($size > APP_STORAGE_READ_BUFFER) {
$response->addHeader('Content-Length', $device->getFileSize($path));
$chunk = 2000000; // Max chunk of 2 mb
for ($i=0; $i < ceil($size / $chunk); $i++) {
2021-07-15 23:45:26 +12:00
$response->chunk($device->read($path, ($i * $chunk), min($chunk, $size - ($i * $chunk))), (($i + 1) * $chunk) >= $size);
2021-07-15 22:35:08 +12:00
}
} else {
$response->send($device->read($path));
}
});
App::put('/v1/storage/buckets/:bucketId/files/:fileId')
->alias('/v1/storage/files/:fileId', ['bucketId' => 'default'])
->desc('Update File')
->groups(['api', 'storage'])
->label('scope', 'files.write')
->label('event', 'storage.files.update')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'storage')
->label('sdk.method', 'updateFile')
->label('sdk.description', '/docs/references/storage/update-file.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_FILE)
->param('bucketId', null, new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).')
->param('fileId', '', new UID(), 'File unique ID.')
2021-09-30 19:23:19 +13:00
->param('read', null, new Permissions(), 'An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.')
->param('write', null, new Permissions(), 'An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.')
->inject('response')
->inject('dbForInternal')
->inject('audits')
2021-08-16 19:25:20 +12:00
->inject('usage')
->action(function ($bucketId, $fileId, $read, $write, $response, $dbForInternal, $audits, $usage) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Appwrite\Event\Event $audits */
/** @var Appwrite\Stats\Stats $usage */
$bucket = $dbForInternal->getDocument('buckets', $bucketId);
2021-07-07 22:07:11 +12:00
if ($bucket->isEmpty()) {
throw new Exception('Bucket not found', 404);
}
2021-09-02 22:49:07 +12:00
$file = $dbForInternal->getDocument('bucket_' . $bucketId, $fileId);
2021-06-20 23:25:48 +12:00
if ($file->isEmpty() || $file->getAttribute('bucketId') != $bucketId) {
throw new Exception('File not found', 404);
}
2021-09-02 22:49:07 +12:00
$file = $dbForInternal->updateDocument('bucket_' . $bucketId, $fileId, $file
->setAttribute('$read', $read)
->setAttribute('$write', $write)
);
$audits
->setParam('event', 'storage.files.update')
2021-08-30 00:06:33 +12:00
->setParam('resource', 'file/'.$file->getId())
;
2021-08-16 19:25:20 +12:00
$usage
->setParam('storage.files.update', 1)
2021-09-10 20:49:16 +12:00
->setParam('bucketId', $bucketId)
2021-08-16 19:25:20 +12:00
;
2021-07-26 02:47:18 +12:00
$response->dynamic($file, Response::MODEL_FILE);
});
App::delete('/v1/storage/buckets/:bucketId/files/:fileId')
->alias('/v1/storage/files/:fileId', ['bucketId' => 'default'])
->desc('Delete File')
->groups(['api', 'storage'])
->label('scope', 'files.write')
->label('event', 'storage.files.delete')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'storage')
->label('sdk.method', 'deleteFile')
->label('sdk.description', '/docs/references/storage/delete-file.md')
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
->label('sdk.response.model', Response::MODEL_NONE)
->param('bucketId', null, new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).')
->param('fileId', '', new UID(), 'File unique ID.')
->inject('response')
->inject('dbForInternal')
->inject('events')
->inject('audits')
->inject('usage')
->action(function ($bucketId, $fileId, $response, $dbForInternal, $events, $audits, $usage) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Appwrite\Event\Event $events */
/** @var Appwrite\Event\Event $audits */
2021-08-16 19:25:20 +12:00
/** @var Appwrite\Stats\Stats $usage */
$bucket = $dbForInternal->getDocument('buckets', $bucketId);
2021-07-07 22:07:11 +12:00
if ($bucket->isEmpty()) {
throw new Exception('Bucket not found', 404);
}
2021-09-02 22:49:07 +12:00
$file = $dbForInternal->getDocument('bucket_' . $bucketId, $fileId);
2019-05-09 18:54:39 +12:00
if ($file->isEmpty() || $file->getAttribute('bucketId') != $bucketId) {
2020-06-30 23:09:28 +12:00
throw new Exception('File not found', 404);
}
2019-05-09 18:54:39 +12:00
2020-07-15 09:20:46 +12:00
$device = Storage::getDevice('files');
2019-05-09 18:54:39 +12:00
2020-06-30 23:09:28 +12:00
if ($device->delete($file->getAttribute('path', ''))) {
2021-09-02 22:49:07 +12:00
if (!$dbForInternal->deleteDocument('bucket_' . $bucketId, $fileId)) {
2020-06-30 23:09:28 +12:00
throw new Exception('Failed to remove file from DB', 500);
2019-05-09 18:54:39 +12:00
}
2021-07-08 23:26:11 +12:00
} else {
throw new Exception('Failed to delete file from device', 500);
2020-06-30 23:09:28 +12:00
}
2021-07-07 22:07:11 +12:00
2020-07-06 02:19:59 +12:00
$audits
2020-06-30 23:09:28 +12:00
->setParam('event', 'storage.files.delete')
2021-08-30 00:06:33 +12:00
->setParam('resource', 'file/'.$file->getId())
2020-06-30 23:09:28 +12:00
;
2019-05-09 18:54:39 +12:00
2020-06-30 23:09:28 +12:00
$usage
->setParam('storage', $file->getAttribute('size', 0) * -1)
2021-08-17 18:33:32 +12:00
->setParam('storage.files.delete', 1)
2021-09-10 20:49:16 +12:00
->setParam('bucketId', $bucketId)
2020-06-30 23:09:28 +12:00
;
2019-05-09 18:54:39 +12:00
2020-12-07 11:14:57 +13:00
$events
2021-07-26 02:47:18 +12:00
->setParam('eventData', $response->output($file, Response::MODEL_FILE))
2020-10-31 08:53:27 +13:00
;
2020-06-30 23:09:28 +12:00
$response->noContent();
});
App::get('/v1/storage/usage')
->desc('Get usage stats for storage')
->groups(['api', 'storage'])
->label('scope', 'files.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'storage')
->label('sdk.method', 'getUsage')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_USAGE_STORAGE)
->param('range', '30d', new WhiteList(['24h', '7d', '30d', '90d'], true), 'Date range.', true)
->inject('response')
->inject('dbForInternal')
->action(function ($range, $response, $dbForInternal) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
$usage = [];
if (App::getEnv('_APP_USAGE_STATS', 'enabled') == 'enabled') {
$period = [
'24h' => [
'period' => '30m',
'limit' => 48,
],
'7d' => [
'period' => '1d',
'limit' => 7,
],
'30d' => [
'period' => '1d',
'limit' => 30,
],
'90d' => [
'period' => '1d',
'limit' => 90,
],
];
$metrics = [
"storage.total",
"storage.files.count"
];
$stats = [];
Authorization::skip(function() use ($dbForInternal, $period, $range, $metrics, &$stats) {
foreach ($metrics as $metric) {
$requestDocs = $dbForInternal->find('stats', [
new Query('period', Query::TYPE_EQUAL, [$period[$range]['period']]),
new Query('metric', Query::TYPE_EQUAL, [$metric]),
], $period[$range]['limit'], 0, ['time'], [Database::ORDER_DESC]);
$stats[$metric] = [];
foreach ($requestDocs as $requestDoc) {
$stats[$metric][] = [
'value' => $requestDoc->getAttribute('value'),
'date' => $requestDoc->getAttribute('time'),
];
}
$stats[$metric] = array_reverse($stats[$metric]);
}
});
$usage = new Document([
'range' => $range,
'storage' => $stats['storage.total'],
'files' => $stats['storage.files.count']
]);
}
$response->dynamic($usage, Response::MODEL_USAGE_STORAGE);
});
App::get('/v1/storage/:bucketId/usage')
2021-08-21 00:10:52 +12:00
->desc('Get usage stats for a storage bucket')
->groups(['api', 'storage'])
->label('scope', 'files.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'storage')
->label('sdk.method', 'getBucketUsage')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_USAGE_BUCKETS)
->param('bucketId', '', new UID(), 'Bucket unique ID.')
->param('range', '30d', new WhiteList(['24h', '7d', '30d', '90d'], true), 'Date range.', true)
->inject('response')
->inject('dbForInternal')
->action(function ($bucketId, $range, $response, $dbForInternal) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
$bucket = $dbForInternal->getDocument('buckets', $bucketId);
if($bucket->isEmpty()) {
throw new Exception('Bucket not found', 404);
}
$usage = [];
if (App::getEnv('_APP_USAGE_STATS', 'enabled') == 'enabled') {
$period = [
'24h' => [
'period' => '30m',
'limit' => 48,
],
'7d' => [
'period' => '1d',
'limit' => 7,
],
'30d' => [
'period' => '1d',
'limit' => 30,
],
'90d' => [
'period' => '1d',
'limit' => 90,
],
];
$metrics = [
"storage.buckets.$bucketId.files.count",
"storage.buckets.$bucketId.files.create",
"storage.buckets.$bucketId.files.read",
"storage.buckets.$bucketId.files.update",
"storage.buckets.$bucketId.files.delete"
];
$stats = [];
Authorization::skip(function() use ($dbForInternal, $period, $range, $metrics, &$stats) {
foreach ($metrics as $metric) {
$requestDocs = $dbForInternal->find('stats', [
new Query('period', Query::TYPE_EQUAL, [$period[$range]['period']]),
new Query('metric', Query::TYPE_EQUAL, [$metric]),
], $period[$range]['limit'], 0, ['time'], [Database::ORDER_DESC]);
$stats[$metric] = [];
foreach ($requestDocs as $requestDoc) {
$stats[$metric][] = [
'value' => $requestDoc->getAttribute('value'),
'date' => $requestDoc->getAttribute('time'),
];
}
$stats[$metric] = array_reverse($stats[$metric]);
}
});
$usage = new Document([
'range' => $range,
'files.count' => $stats["storage.buckets.$bucketId.files.count"],
'files.create' => $stats["storage.buckets.$bucketId.files.create"],
'files.read' => $stats["storage.buckets.$bucketId.files.read"],
'files.update' => $stats["storage.buckets.$bucketId.files.update"],
'files.delete' => $stats["storage.buckets.$bucketId.files.delete"]
]);
}
$response->dynamic($usage, Response::MODEL_USAGE_BUCKETS);
});