2019-05-09 18:54:39 +12:00
< ? php
2021-12-11 05:48:54 +13:00
use Appwrite\Auth\Auth ;
2022-01-19 00:05:04 +13:00
use Appwrite\ClamAV\Network ;
2022-05-09 03:25:01 +12:00
use Appwrite\Event\Delete ;
2022-05-05 00:23:34 +12:00
use Appwrite\Event\Event ;
2022-01-19 00:05:04 +13:00
use Appwrite\Utopia\Database\Validator\CustomId ;
2022-01-16 20:55:55 +13:00
use Appwrite\OpenSSL\OpenSSL ;
2022-01-19 00:05:04 +13:00
use Appwrite\Utopia\Response ;
2020-06-29 05:31:21 +12:00
use Utopia\App ;
2022-01-19 00:05:04 +13:00
use Utopia\Config\Config ;
use Utopia\Database\Database ;
2021-05-03 20:28:31 +12:00
use Utopia\Database\Document ;
2022-07-13 01:32:39 +12:00
use Utopia\Database\DateTime ;
2022-01-16 20:55:55 +13:00
use Utopia\Database\Exception\Duplicate ;
2022-08-25 23:30:26 +12:00
use Utopia\Database\Exception\Authorization as AuthorizationException ;
2022-01-16 20:55:55 +13:00
use Utopia\Database\Exception\Duplicate as DuplicateException ;
use Utopia\Database\Exception\Structure as StructureException ;
2022-08-14 22:33:36 +12:00
use Utopia\Database\ID ;
2022-08-16 00:56:19 +12:00
use Utopia\Database\Permission ;
2022-01-19 00:05:04 +13:00
use Utopia\Database\Query ;
2022-08-16 23:26:38 +12:00
use Utopia\Database\Role ;
2022-01-16 20:55:55 +13:00
use Utopia\Database\Validator\Authorization ;
use Utopia\Database\Validator\Permissions ;
2021-07-26 02:47:18 +12:00
use Utopia\Database\Validator\UID ;
2022-02-17 11:25:29 +13:00
use Appwrite\Extend\Exception ;
2022-08-23 20:37:55 +12:00
use Appwrite\Utopia\Database\Validator\Queries\Buckets ;
2022-08-23 20:49:39 +12:00
use Appwrite\Utopia\Database\Validator\Queries\Files ;
2022-01-19 00:05:04 +13:00
use Utopia\Image\Image ;
2022-01-16 20:55:55 +13:00
use Utopia\Storage\Compression\Algorithms\GZIP ;
2022-08-31 01:46:55 +12:00
use Utopia\Storage\Compression\Algorithms\Zstd ;
2022-05-05 00:23:34 +12:00
use Utopia\Storage\Device ;
2021-01-22 21:28:33 +13:00
use Utopia\Storage\Storage ;
use Utopia\Storage\Validator\File ;
2022-01-16 20:55:55 +13:00
use Utopia\Storage\Validator\FileExt ;
2021-01-22 21:28:33 +13:00
use Utopia\Storage\Validator\FileSize ;
use Utopia\Storage\Validator\Upload ;
2022-01-19 00:05:04 +13:00
use Utopia\Validator\ArrayList ;
2021-07-07 22:07:11 +12:00
use Utopia\Validator\Boolean ;
use Utopia\Validator\HexColor ;
2022-01-19 00:05:04 +13:00
use Utopia\Validator\Range ;
use Utopia\Validator\Text ;
2021-07-07 22:07:11 +12:00
use Utopia\Validator\WhiteList ;
2022-11-18 01:50:17 +13:00
use Utopia\DSN\DSN ;
2022-05-24 02:54:50 +12:00
use Utopia\Swoole\Request ;
2019-05-09 18:54:39 +12:00
2021-06-14 23:32:09 +12:00
App :: post ( '/v1/storage/buckets' )
2022-01-31 19:22:28 +13:00
-> desc ( 'Create bucket' )
2021-06-14 23:32:09 +12:00
-> groups ([ 'api' , 'storage' ])
-> label ( 'scope' , 'buckets.write' )
2022-04-14 00:39:31 +12:00
-> label ( 'event' , 'buckets.[bucketId].create' )
2022-09-05 20:00:08 +12:00
-> label ( 'audits.event' , 'bucket.create' )
2022-09-09 00:16:54 +12:00
-> label ( 'audits.resource' , 'bucket/{response.$id}' )
2022-08-11 14:18:22 +12:00
-> label ( 'usage.metric' , 'buckets.{scope}.requests.create' )
2021-06-16 17:26:34 +12:00
-> 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 )
2022-10-04 09:22:28 +13:00
-> param ( 'bucketId' , '' , new CustomId (), 'Unique Id. Choose your own unique ID or pass the string `ID.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.' )
2022-02-23 02:19:22 +13:00
-> param ( 'name' , '' , new Text ( 128 ), 'Bucket name' )
2022-08-27 15:19:00 +12:00
-> param ( 'permissions' , null , new Permissions ( APP_LIMIT_ARRAY_PARAMS_SIZE ), 'An array of permission strings. By default no user is granted with any permissions. [Learn more about permissions](/docs/permissions).' , true )
2022-09-02 15:39:54 +12:00
-> param ( 'fileSecurity' , false , new Boolean ( true ), 'Enables configuring permissions for individual file. A user needs one of file or bucket level permissions to access a file. [Learn more about permissions](/docs/permissions).' , true )
2022-02-23 18:50:55 +13:00
-> param ( 'enabled' , true , new Boolean ( true ), 'Is bucket enabled?' , true )
2022-05-10 22:28:16 +12:00
-> param ( 'maximumFileSize' , ( int ) App :: getEnv ( '_APP_STORAGE_LIMIT' , 0 ), new Range ( 1 , ( int ) App :: getEnv ( '_APP_STORAGE_LIMIT' , 0 )), 'Maximum file size allowed in bytes. Maximum allowed value is ' . Storage :: human ( App :: getEnv ( '_APP_STORAGE_LIMIT' , 0 ), 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 )
2022-05-01 19:54:58 +12:00
-> 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 )
2022-08-31 22:24:13 +12:00
-> param ( 'compression' , 'none' , new WhiteList ([ COMPRESSION_TYPE_NONE , COMPRESSION_TYPE_GZIP , COMPRESSION_TYPE_ZSTD ]), 'Compression algorithm choosen for compression. Can be one of ' . COMPRESSION_TYPE_NONE . ', [' . COMPRESSION_TYPE_GZIP . '](https://en.wikipedia.org/wiki/Gzip), or [' . COMPRESSION_TYPE_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 )
2022-02-23 18:50:55 +13:00
-> 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 )
2021-06-14 23:32:09 +12:00
-> inject ( 'response' )
2022-01-06 20:26:23 +13:00
-> inject ( 'dbForProject' )
2022-04-14 00:39:31 +12:00
-> inject ( 'events' )
2022-08-31 00:55:26 +12:00
-> action ( function ( string $bucketId , string $name , ? array $permissions , bool $fileSecurity , bool $enabled , int $maximumFileSize , array $allowedFileExtensions , string $compression , bool $encryption , bool $antivirus , Response $response , Database $dbForProject , Event $events ) {
2021-06-14 23:59:47 +12:00
2022-08-15 02:22:38 +12:00
$bucketId = $bucketId === 'unique()' ? ID :: unique () : $bucketId ;
2022-08-16 17:54:44 +12:00
2022-08-23 13:42:25 +12:00
// Map aggregate permissions into the multiple permissions they represent.
$permissions = Permission :: aggregate ( $permissions );
2022-08-16 20:33:06 +12:00
2021-09-02 22:49:07 +12:00
try {
2021-12-23 21:31:32 +13:00
$files = Config :: getParam ( 'collections' , [])[ 'files' ] ? ? [];
2022-01-16 20:55:55 +13:00
if ( empty ( $files )) {
2022-07-27 02:24:32 +12:00
throw new Exception ( Exception :: GENERAL_SERVER_ERROR , 'Files collection is not configured.' );
2021-12-13 22:17:53 +13:00
}
$attributes = [];
$indexes = [];
foreach ( $files [ 'attributes' ] as $attribute ) {
$attributes [] = new Document ([
2022-08-15 23:24:31 +12:00
'$id' => $attribute [ '$id' ],
2021-12-13 22:17:53 +13:00
'type' => $attribute [ 'type' ],
'size' => $attribute [ 'size' ],
'required' => $attribute [ 'required' ],
'signed' => $attribute [ 'signed' ],
'array' => $attribute [ 'array' ],
'filters' => $attribute [ 'filters' ],
2022-03-27 21:01:50 +13:00
'default' => $attribute [ 'default' ] ? ? null ,
'format' => $attribute [ 'format' ] ? ? ''
2021-12-13 22:17:53 +13:00
]);
}
foreach ( $files [ 'indexes' ] as $index ) {
$indexes [] = new Document ([
2022-08-15 23:24:31 +12:00
'$id' => $index [ '$id' ],
2021-12-13 22:17:53 +13:00
'type' => $index [ 'type' ],
'attributes' => $index [ 'attributes' ],
'lengths' => $index [ 'lengths' ],
'orders' => $index [ 'orders' ],
]);
}
2022-08-08 22:58:36 +12:00
$dbForProject -> createDocument ( 'buckets' , new Document ([
2022-08-15 02:22:38 +12:00
'$id' => $bucketId ,
2022-08-15 23:24:31 +12:00
'$collection' => 'buckets' ,
2022-08-04 16:20:11 +12:00
'$permissions' => $permissions ,
2021-11-11 19:20:52 +13:00
'name' => $name ,
2021-09-02 22:49:07 +12:00
'maximumFileSize' => $maximumFileSize ,
'allowedFileExtensions' => $allowedFileExtensions ,
2022-08-27 15:19:51 +12:00
'fileSecurity' => $fileSecurity ,
'enabled' => $enabled ,
2022-08-31 00:55:26 +12:00
'compression' => $compression ,
2022-08-27 15:19:51 +12:00
'encryption' => $encryption ,
'antivirus' => $antivirus ,
2021-10-17 19:06:13 +13:00
'search' => implode ( ' ' , [ $bucketId , $name ]),
2021-09-02 22:49:07 +12:00
]));
2022-02-18 15:14:57 +13:00
$bucket = $dbForProject -> getDocument ( 'buckets' , $bucketId );
$dbForProject -> createCollection ( 'bucket_' . $bucket -> getInternalId (), $attributes , $indexes );
2022-08-08 22:58:36 +12:00
} catch ( Duplicate ) {
2022-07-27 02:24:32 +12:00
throw new Exception ( Exception :: STORAGE_BUCKET_ALREADY_EXISTS );
2021-09-02 22:49:07 +12:00
}
2021-06-14 23:59:47 +12:00
2022-04-14 00:39:31 +12:00
$events
-> setParam ( 'bucketId' , $bucket -> getId ())
2021-06-14 23:59:47 +12:00
;
2022-09-07 23:11:10 +12:00
$response
-> setStatusCode ( Response :: STATUS_CODE_CREATED )
-> 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' )
2022-08-11 14:18:22 +12:00
-> label ( 'usage.metric' , 'buckets.{scope}.requests.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 )
2022-08-23 20:37:55 +12:00
-> param ( 'queries' , [], new Buckets (), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode ( ', ' , Buckets :: ALLOWED_ATTRIBUTES ), true )
2021-06-15 19:23:22 +12:00
-> param ( 'search' , '' , new Text ( 256 ), 'Search term to filter your list results. Max length: 256 chars.' , true )
-> inject ( 'response' )
2022-01-06 20:26:23 +13:00
-> inject ( 'dbForProject' )
2022-08-25 21:59:28 +12:00
-> action ( function ( array $queries , string $search , Response $response , Database $dbForProject ) {
2021-06-15 19:23:22 +12:00
2022-08-23 20:37:55 +12:00
$queries = Query :: parseQueries ( $queries );
2022-01-16 20:55:55 +13:00
2022-08-12 11:53:52 +12:00
if ( ! empty ( $search )) {
2022-08-23 20:37:55 +12:00
$queries [] = Query :: search ( 'search' , $search );
2022-08-12 11:53:52 +12:00
}
2022-08-23 20:37:55 +12:00
// Get cursor document if there was a cursor query
2022-08-31 11:31:43 +12:00
$cursor = Query :: getByType ( $queries , Query :: TYPE_CURSORAFTER , Query :: TYPE_CURSORBEFORE );
$cursor = reset ( $cursor );
2022-08-30 23:55:23 +12:00
if ( $cursor ) {
2022-08-23 20:37:55 +12:00
/** @var Query $cursor */
$bucketId = $cursor -> getValue ();
$cursorDocument = $dbForProject -> getDocument ( 'buckets' , $bucketId );
2021-09-02 20:30:10 +12:00
2022-08-12 11:53:52 +12:00
if ( $cursorDocument -> isEmpty ()) {
2022-08-23 20:37:55 +12:00
throw new Exception ( Exception :: GENERAL_CURSOR_NOT_FOUND , " Bucket ' { $bucketId } ' for the 'cursor' value not found. " );
2021-09-02 20:30:10 +12:00
}
2022-08-12 11:53:52 +12:00
2022-08-23 20:37:55 +12:00
$cursor -> setValue ( $cursorDocument );
2021-09-02 20:30:10 +12:00
}
2021-06-15 19:23:22 +12:00
2022-08-23 20:37:55 +12:00
$filterQueries = Query :: groupByType ( $queries )[ 'filters' ];
2021-07-27 22:19:39 +12:00
$response -> dynamic ( new Document ([
2022-08-23 20:37:55 +12:00
'buckets' => $dbForProject -> find ( 'buckets' , $queries ),
2022-08-12 11:53:52 +12:00
'total' => $dbForProject -> count ( 'buckets' , $filterQueries , 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' )
2022-08-11 14:18:22 +12:00
-> label ( 'usage.metric' , 'buckets.{scope}.requests.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' )
2022-01-06 20:26:23 +13:00
-> inject ( 'dbForProject' )
2022-08-11 14:18:22 +12:00
-> action ( function ( string $bucketId , Response $response , Database $dbForProject ) {
2021-06-15 19:48:59 +12:00
2022-01-06 20:26:23 +13:00
$bucket = $dbForProject -> getDocument ( 'buckets' , $bucketId );
2021-06-15 19:48:59 +12:00
2021-06-17 17:28:27 +12:00
if ( $bucket -> isEmpty ()) {
2022-07-27 02:24:32 +12:00
throw new Exception ( Exception :: STORAGE_BUCKET_NOT_FOUND );
2021-06-15 19:48:59 +12:00
}
2021-07-27 22:19:39 +12:00
$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' )
2022-04-14 00:39:31 +12:00
-> label ( 'event' , 'buckets.[bucketId].update' )
2022-09-05 20:00:08 +12:00
-> label ( 'audits.event' , 'bucket.update' )
2022-09-09 00:16:54 +12:00
-> label ( 'audits.resource' , 'bucket/{response.$id}' )
2022-08-11 14:18:22 +12:00
-> label ( 'usage.metric' , 'buckets.{scope}.requests.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 )
2022-08-27 15:19:00 +12:00
-> param ( 'permissions' , null , new Permissions ( APP_LIMIT_ARRAY_PARAMS_SIZE ), 'An array of permission strings. By default the current permissions are inherited. [Learn more about permissions](/docs/permissions).' , true )
2022-09-03 12:44:33 +12:00
-> param ( 'fileSecurity' , false , new Boolean ( true ), 'Enables configuring permissions for individual file. A user needs one of file or bucket level permissions to access a file. [Learn more about permissions](/docs/permissions).' , true )
2022-02-23 18:50:55 +13:00
-> param ( 'enabled' , true , new Boolean ( true ), 'Is bucket enabled?' , true )
2022-05-10 22:28:16 +12:00
-> param ( 'maximumFileSize' , null , new Range ( 1 , ( int ) App :: getEnv ( '_APP_STORAGE_LIMIT' , 0 )), 'Maximum file size allowed in bytes. Maximum allowed value is ' . Storage :: human (( int ) App :: getEnv ( '_APP_STORAGE_LIMIT' , 0 ), 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 )
2022-05-01 19:54:58 +12:00
-> 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 )
2022-08-31 22:24:13 +12:00
-> param ( 'compression' , 'none' , new WhiteList ([ COMPRESSION_TYPE_NONE , COMPRESSION_TYPE_GZIP , COMPRESSION_TYPE_ZSTD ]), 'Compression algorithm choosen for compression. Can be one of ' . COMPRESSION_TYPE_NONE . ', [' . COMPRESSION_TYPE_GZIP . '](https://en.wikipedia.org/wiki/Gzip), or [' . COMPRESSION_TYPE_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 )
2022-02-23 18:50:55 +13:00
-> 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 )
2021-06-16 21:59:47 +12:00
-> inject ( 'response' )
2022-01-06 20:26:23 +13:00
-> inject ( 'dbForProject' )
2022-04-14 00:39:31 +12:00
-> inject ( 'events' )
2022-08-31 01:50:38 +12:00
-> action ( function ( string $bucketId , string $name , ? array $permissions , bool $fileSecurity , bool $enabled , ? int $maximumFileSize , array $allowedFileExtensions , string $compression , bool $encryption , bool $antivirus , Response $response , Database $dbForProject , Event $events ) {
2022-01-06 20:26:23 +13:00
$bucket = $dbForProject -> getDocument ( 'buckets' , $bucketId );
2021-06-16 21:59:47 +12:00
2021-06-17 20:29:02 +12:00
if ( $bucket -> isEmpty ()) {
2022-07-27 02:24:32 +12:00
throw new Exception ( Exception :: STORAGE_BUCKET_NOT_FOUND );
2021-06-16 21:59:47 +12:00
}
2022-08-02 21:19:15 +12:00
$permissions ? ? = $bucket -> getPermissions ();
2022-04-14 00:39:31 +12:00
$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
2022-08-16 17:54:44 +12:00
/**
* Map aggregate permissions into the multiple permissions they represent ,
* accounting for the resource type given that some types not allowed specific permissions .
*/
2022-08-23 13:42:25 +12:00
// Map aggregate permissions into the multiple permissions they represent.
$permissions = Permission :: aggregate ( $permissions );
2022-08-16 20:33:06 +12:00
2022-01-06 20:26:23 +13:00
$bucket = $dbForProject -> updateDocument ( 'buckets' , $bucket -> getId (), $bucket
2022-01-16 20:55:55 +13:00
-> setAttribute ( 'name' , $name )
2022-08-02 21:19:15 +12:00
-> setAttribute ( '$permissions' , $permissions )
2022-01-16 20:55:55 +13:00
-> setAttribute ( 'maximumFileSize' , $maximumFileSize )
-> setAttribute ( 'allowedFileExtensions' , $allowedFileExtensions )
2022-08-27 15:19:51 +12:00
-> setAttribute ( 'fileSecurity' , $fileSecurity )
-> setAttribute ( 'enabled' , $enabled )
-> setAttribute ( 'encryption' , $encryption )
2022-08-31 01:50:38 +12:00
-> setAttribute ( 'compression' , $compression )
2022-08-27 15:19:51 +12:00
-> setAttribute ( 'antivirus' , $antivirus ));
2021-06-16 21:59:47 +12:00
2022-04-14 00:39:31 +12:00
$events
-> setParam ( 'bucketId' , $bucket -> getId ())
2021-06-16 21:59:47 +12:00
;
2021-07-27 22:19:39 +12:00
$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' )
2022-09-05 20:00:08 +12:00
-> label ( 'audits.event' , 'bucket.delete' )
2022-04-14 00:39:31 +12:00
-> label ( 'event' , 'buckets.[bucketId].delete' )
2022-09-09 00:16:54 +12:00
-> label ( 'audits.resource' , 'bucket/{request.bucketId}' )
2022-08-11 14:18:22 +12:00
-> label ( 'usage.metric' , 'buckets.{scope}.requests.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' )
2022-01-06 20:26:23 +13:00
-> inject ( 'dbForProject' )
2021-06-16 22:17:14 +12:00
-> inject ( 'deletes' )
2021-06-17 20:23:21 +12:00
-> inject ( 'events' )
2022-08-13 15:14:28 +12:00
-> action ( function ( string $bucketId , Response $response , Database $dbForProject , Delete $deletes , Event $events ) {
2022-01-06 20:26:23 +13:00
$bucket = $dbForProject -> getDocument ( 'buckets' , $bucketId );
2019-05-09 18:54:39 +12:00
2021-06-17 20:27:40 +12:00
if ( $bucket -> isEmpty ()) {
2022-07-27 02:24:32 +12:00
throw new Exception ( Exception :: STORAGE_BUCKET_NOT_FOUND );
2021-06-16 22:17:14 +12:00
}
2022-01-16 20:55:55 +13:00
if ( ! $dbForProject -> deleteDocument ( 'buckets' , $bucketId )) {
2022-08-14 18:56:12 +12:00
throw new Exception ( Exception :: GENERAL_SERVER_ERROR , 'Failed to remove bucket from DB' );
2021-09-15 16:54:49 +12:00
}
2022-01-16 20:55:55 +13:00
2021-06-16 22:17:14 +12:00
$deletes
2022-04-18 08:34:32 +12:00
-> setType ( DELETE_TYPE_DOCUMENT )
-> setDocument ( $bucket );
2021-06-16 22:17:14 +12:00
2021-06-17 20:23:21 +12:00
$events
2022-04-14 00:39:31 +12:00
-> setParam ( 'bucketId' , $bucket -> getId ())
-> setPayload ( $response -> output ( $bucket , Response :: MODEL_BUCKET ))
2021-06-17 20:23:21 +12:00
;
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' ])
2020-02-01 11:34:07 +13:00
-> desc ( 'Create File' )
2020-06-26 06:32:12 +12:00
-> groups ([ 'api' , 'storage' ])
2020-02-01 11:34:07 +13:00
-> label ( 'scope' , 'files.write' )
2022-09-05 20:00:08 +12:00
-> label ( 'audits.event' , 'file.create' )
2022-04-14 00:39:31 +12:00
-> label ( 'event' , 'buckets.[bucketId].files.[fileId].create' )
2022-09-09 00:16:54 +12:00
-> label ( 'audits.resource' , 'file/{response.$id}' )
2022-08-11 14:18:22 +12:00
-> label ( 'usage.metric' , 'files.{scope}.requests.create' )
2022-08-21 14:03:19 +12:00
-> label ( 'usage.params' , [ 'bucketId:{request.bucketId}' ])
2022-08-31 15:58:32 +12:00
-> label ( 'abuse-key' , 'ip:{ip},method:{method},url:{url},userId:{userId}' )
2022-08-31 11:34:17 +12:00
-> label ( 'abuse-limit' , APP_LIMIT_WRITE_RATE_DEFAULT )
-> label ( 'abuse-time' , APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT )
2021-04-16 19:22:17 +12:00
-> label ( 'sdk.auth' , [ APP_AUTH_TYPE_SESSION , APP_AUTH_TYPE_KEY , APP_AUTH_TYPE_JWT ])
2020-02-01 11:34:07 +13:00
-> label ( 'sdk.namespace' , 'storage' )
-> label ( 'sdk.method' , 'createFile' )
-> label ( 'sdk.description' , '/docs/references/storage/create-file.md' )
2020-11-12 10:02:24 +13:00
-> label ( 'sdk.request.type' , 'multipart/form-data' )
2020-04-11 06:59:14 +12:00
-> label ( 'sdk.methodType' , 'upload' )
2020-11-12 10:02:24 +13:00
-> label ( 'sdk.response.code' , Response :: STATUS_CODE_CREATED )
-> label ( 'sdk.response.type' , Response :: CONTENT_TYPE_JSON )
-> label ( 'sdk.response.model' , Response :: MODEL_FILE )
2022-09-19 22:05:42 +12:00
-> param ( 'bucketId' , '' , new UID (), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).' )
2022-10-04 09:22:28 +13:00
-> param ( 'fileId' , '' , new CustomId (), 'File ID. Choose your own unique ID or pass the string `ID.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.' )
2020-09-11 02:40:14 +12:00
-> param ( 'file' , [], new File (), 'Binary file.' , false )
2022-09-06 21:17:25 +12:00
-> param ( 'permissions' , null , new Permissions ( APP_LIMIT_ARRAY_PARAMS_SIZE , [ Database :: PERMISSION_READ , Database :: PERMISSION_UPDATE , Database :: PERMISSION_DELETE , Database :: PERMISSION_WRITE ]), 'An array of permission strings. By default the current user is granted with all permissions. [Learn more about permissions](/docs/permissions).' , true )
2020-12-27 05:48:36 +13:00
-> inject ( 'request' )
-> inject ( 'response' )
2021-12-28 01:45:23 +13:00
-> inject ( 'dbForProject' )
2021-03-22 11:17:20 +13:00
-> inject ( 'user' )
2022-02-16 14:30:27 +13:00
-> inject ( 'events' )
2021-11-19 22:42:09 +13:00
-> inject ( 'mode' )
2021-12-13 21:08:27 +13:00
-> inject ( 'deviceFiles' )
-> inject ( 'deviceLocal' )
2022-08-16 00:16:32 +12:00
-> inject ( 'deletes' )
2022-08-25 18:21:15 +12:00
-> action ( function ( string $bucketId , string $fileId , mixed $file , ? array $permissions , Request $request , Response $response , Database $dbForProject , Document $user , Event $events , string $mode , Device $deviceFiles , Device $deviceLocal , Delete $deletes ) {
2022-08-08 22:58:36 +12:00
2022-03-20 19:31:34 +13:00
$bucket = Authorization :: skip ( fn () => $dbForProject -> getDocument ( 'buckets' , $bucketId ));
2020-06-30 09:43:34 +12:00
2022-08-08 22:58:36 +12:00
if ( $bucket -> isEmpty () || ( ! $bucket -> getAttribute ( 'enabled' ) && $mode !== APP_MODE_ADMIN )) {
2022-07-27 02:24:32 +12:00
throw new Exception ( Exception :: STORAGE_BUCKET_NOT_FOUND );
2021-06-17 22:10:58 +12:00
}
2022-08-08 22:58:36 +12:00
2022-08-25 01:24:54 +12:00
$validator = new Authorization ( Database :: PERMISSION_CREATE );
2022-08-14 02:24:28 +12:00
if ( ! $validator -> isValid ( $bucket -> getCreate ())) {
2022-08-16 20:30:00 +12:00
throw new Exception ( Exception :: USER_UNAUTHORIZED );
2022-08-14 02:24:28 +12:00
}
2022-08-23 13:42:25 +12:00
$allowedPermissions = [
Database :: PERMISSION_READ ,
Database :: PERMISSION_UPDATE ,
Database :: PERMISSION_DELETE ,
];
2022-08-16 17:54:44 +12:00
2022-08-23 13:42:25 +12:00
// Map aggregate permissions to into the set of individual permissions they represent.
$permissions = Permission :: aggregate ( $permissions , $allowedPermissions );
// Add permissions for current the user if none were provided.
2022-08-16 00:56:19 +12:00
if ( \is_null ( $permissions )) {
$permissions = [];
2022-08-16 01:16:20 +12:00
if ( ! empty ( $user -> getId ())) {
2022-08-16 00:56:19 +12:00
foreach ( $allowedPermissions as $permission ) {
2022-08-16 01:16:20 +12:00
$permissions [] = ( new Permission ( $permission , 'user' , $user -> getId ())) -> toString ();
2022-08-16 00:56:19 +12:00
}
}
2022-08-02 21:19:15 +12:00
}
2022-08-16 00:56:19 +12:00
// Users can only manage their own roles, API keys and Admin users can manage any
$roles = Authorization :: getRoles ();
if ( ! Auth :: isAppUser ( $roles ) && ! Auth :: isPrivilegedUser ( $roles )) {
foreach ( Database :: PERMISSIONS as $type ) {
foreach ( $permissions as $permission ) {
2022-08-16 23:26:38 +12:00
$permission = Permission :: parse ( $permission );
if ( $permission -> getPermission () != $type ) {
2022-08-16 00:56:19 +12:00
continue ;
}
2022-08-16 23:26:38 +12:00
$role = ( new Role (
$permission -> getRole (),
$permission -> getIdentifier (),
$permission -> getDimension ()
)) -> toString ();
2022-08-16 00:56:19 +12:00
if ( ! Authorization :: isRole ( $role )) {
2022-08-25 15:51:21 +12:00
throw new Exception ( Exception :: USER_UNAUTHORIZED , 'Permissions must be one of: (' . \implode ( ', ' , $roles ) . ')' );
2022-08-16 00:56:19 +12:00
}
}
}
2021-12-11 05:48:54 +13:00
}
2020-06-30 09:43:34 +12:00
2021-06-24 19:10:21 +12:00
$maximumFileSize = $bucket -> getAttribute ( 'maximumFileSize' , 0 );
2021-07-07 22:07:11 +12:00
if ( $maximumFileSize > ( int ) App :: getEnv ( '_APP_STORAGE_LIMIT' , 0 )) {
2022-07-27 02:24:32 +12:00
throw new Exception ( Exception :: GENERAL_SERVER_ERROR , 'Maximum bucket file size is larger than _APP_STORAGE_LIMIT' );
2021-06-24 19:10:21 +12:00
}
2021-12-14 00:46:59 +13:00
$file = $request -> getFiles ( 'file' );
2020-06-30 09:43:34 +12:00
if ( empty ( $file )) {
2022-07-27 02:24:32 +12:00
throw new Exception ( Exception :: STORAGE_FILE_EMPTY );
2020-06-30 09:43:34 +12:00
}
2020-02-01 11:34:07 +13:00
2020-06-30 09:43:34 +12:00
// 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' ];
2021-11-30 21:04:19 +13:00
$fileSize = ( \is_array ( $file [ 'size' ]) && isset ( $file [ 'size' ][ 0 ])) ? $file [ 'size' ][ 0 ] : $file [ 'size' ];
2021-07-07 22:07:11 +12:00
$contentRange = $request -> getHeader ( 'content-range' );
2022-08-15 02:22:38 +12:00
$fileId = $fileId === 'unique()' ? ID :: unique () : $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 ();
2021-11-30 21:04:19 +13:00
$fileSize = $request -> getContentRangeSize ();
2021-09-27 19:04:17 +13:00
$fileId = $request -> getHeader ( 'x-appwrite-id' , $fileId );
2021-12-10 23:37:35 +13:00
if ( is_null ( $start ) || is_null ( $end ) || is_null ( $fileSize )) {
2022-07-27 02:24:32 +12:00
throw new Exception ( Exception :: STORAGE_INVALID_CONTENT_RANGE );
2021-07-07 22:07:11 +12:00
}
2020-02-01 11:34:07 +13:00
2021-12-07 21:15:42 +13:00
if ( $end === $fileSize ) {
2021-12-14 00:46:59 +13:00
//if it's a last chunks the chunk size might differ, so we set the $chunks and $chunk to -1 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-11-30 21:04:19 +13:00
$chunks = ( int ) ceil ( $fileSize / ( $end + 1 - $start ));
2021-11-25 17:47:40 +13:00
$chunk = ( int ) ( $start / ( $end + 1 - $start )) + 1 ;
2021-07-07 22:07:11 +12:00
}
}
2020-02-01 11:34:07 +13:00
2022-01-16 20:55:55 +13:00
/**
2022-08-15 16:29:44 +12:00
* Validators
*/
2021-12-14 00:46:59 +13:00
// Check if file type is allowed
$allowedFileExtensions = $bucket -> getAttribute ( 'allowedFileExtensions' , []);
$fileExt = new FileExt ( $allowedFileExtensions );
2021-07-07 22:07:11 +12:00
if ( ! empty ( $allowedFileExtensions ) && ! $fileExt -> isValid ( $fileName )) {
2022-08-14 19:02:41 +12:00
throw new Exception ( Exception :: STORAGE_FILE_TYPE_UNSUPPORTED , 'File extension not allowed' );
2020-06-30 09:43:34 +12:00
}
2020-02-01 11:34:07 +13:00
2021-12-14 00:46:59 +13:00
// Check if file size is exceeding allowed limit
$fileSizeValidator = new FileSize ( $maximumFileSize );
if ( ! $fileSizeValidator -> isValid ( $fileSize )) {
2022-08-14 18:56:12 +12:00
throw new Exception ( Exception :: STORAGE_INVALID_FILE_SIZE , 'File size not allowed' );
2021-06-17 22:10:58 +12:00
}
2020-02-01 11:34:07 +13:00
2021-12-14 00:46:59 +13:00
$upload = new Upload ();
2021-07-07 22:07:11 +12:00
if ( ! $upload -> isValid ( $fileTmpName )) {
2022-07-27 02:24:32 +12:00
throw new Exception ( Exception :: STORAGE_INVALID_FILE );
2020-06-30 09:43:34 +12:00
}
2020-02-01 11:34:07 +13:00
2020-06-30 09:43:34 +12:00
// Save to storage
2022-05-24 02:54:50 +12:00
$fileSize ? ? = $deviceLocal -> getFileSize ( $fileTmpName );
2021-12-13 21:08:27 +13:00
$path = $deviceFiles -> getPath ( $fileId . '.' . \pathinfo ( $fileName , PATHINFO_EXTENSION ));
$path = str_ireplace ( $deviceFiles -> getRoot (), $deviceFiles -> getRoot () . DIRECTORY_SEPARATOR . $bucket -> getId (), $path ); // Add bucket id to path after root
2021-10-08 21:39:37 +13:00
2022-08-15 22:11:17 +12:00
$file = $dbForProject -> getDocument ( 'bucket_' . $bucket -> getInternalId (), $fileId );
2020-02-01 11:34:07 +13:00
2021-12-13 21:08:27 +13:00
$metadata = [ 'content_type' => $deviceLocal -> getFileMimeType ( $fileTmpName )];
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-11-25 17:47:40 +13:00
$metadata = $file -> getAttribute ( 'metadata' , []);
2021-12-07 20:30:39 +13:00
if ( $chunk === - 1 ) {
2021-11-25 17:47:40 +13:00
$chunk = $chunks ;
2021-06-17 22:10:58 +12:00
}
}
2020-02-01 11:34:07 +13:00
2021-12-13 21:08:27 +13:00
$chunksUploaded = $deviceFiles -> upload ( $fileTmpName , $path , $chunk , $chunks , $metadata );
2021-07-13 19:58:16 +12:00
if ( empty ( $chunksUploaded )) {
2022-07-27 02:24:32 +12:00
throw new Exception ( Exception :: GENERAL_SERVER_ERROR , 'Failed uploading file' );
2021-06-17 22:10:58 +12:00
}
2020-02-01 11:34:07 +13:00
2021-12-07 20:30:39 +13:00
if ( $chunksUploaded === $chunks ) {
2022-11-18 06:43:59 +13:00
if ( App :: getEnv ( '_APP_STORAGE_ANTIVIRUS' ) === 'enabled' && $bucket -> getAttribute ( 'antivirus' , true ) && $fileSize <= APP_LIMIT_ANTIVIRUS && $deviceFiles -> getType () === Storage :: DEVICE_LOCAL ) {
2022-05-24 02:54:50 +12:00
$antivirus = new Network (
App :: getEnv ( '_APP_STORAGE_ANTIVIRUS_HOST' , 'clamav' ),
( int ) App :: getEnv ( '_APP_STORAGE_ANTIVIRUS_PORT' , 3310 )
);
2022-01-16 20:55:55 +13:00
if ( ! $antivirus -> fileScan ( $path )) {
2021-12-13 21:08:27 +13:00
$deviceFiles -> delete ( $path );
2022-07-27 02:24:32 +12:00
throw new Exception ( Exception :: STORAGE_INVALID_FILE );
2021-07-07 22:07:11 +12:00
}
2020-02-01 11:34:07 +13:00
}
2021-12-13 21:08:27 +13:00
$mimeType = $deviceFiles -> getFileMimeType ( $path ); // Get mime-type before compression and encryption
2021-07-13 20:16:24 +12:00
$data = '' ;
2021-07-07 22:07:11 +12:00
// Compression
2022-08-31 01:46:55 +12:00
$algorithm = $bucket -> getAttribute ( 'compression' , 'none' );
if ( $fileSize <= APP_STORAGE_READ_BUFFER && $algorithm != 'none' ) {
2021-12-13 21:08:27 +13:00
$data = $deviceFiles -> read ( $path );
2022-08-31 14:56:44 +12:00
switch ( $algorithm ) {
2022-08-31 01:46:55 +12:00
case 'zstd' :
$compressor = new Zstd ();
2022-08-31 14:59:51 +12:00
break ;
2022-08-31 01:46:55 +12:00
case 'gzip' :
default :
$compressor = new GZIP ();
break ;
}
2021-07-13 20:16:24 +12:00
$data = $compressor -> compress ( $data );
2021-06-17 22:10:58 +12:00
}
2020-02-01 11:34:07 +13:00
2021-11-30 21:04:19 +13:00
if ( $bucket -> getAttribute ( 'encryption' , true ) && $fileSize <= APP_STORAGE_READ_BUFFER ) {
2022-01-16 20:55:55 +13:00
if ( empty ( $data )) {
2021-12-13 21:08:27 +13:00
$data = $deviceFiles -> 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 );
}
2020-06-30 09:43:34 +12:00
2021-12-09 23:44:44 +13:00
if ( ! empty ( $data )) {
2021-12-13 21:08:27 +13:00
if ( ! $deviceFiles -> write ( $path , $data , $mimeType )) {
2022-07-27 02:24:32 +12:00
throw new Exception ( Exception :: GENERAL_SERVER_ERROR , 'Failed to save file' );
2021-07-08 23:26:11 +12:00
}
2021-07-07 22:07:11 +12:00
}
2021-06-17 22:10:58 +12:00
2021-12-13 21:08:27 +13:00
$sizeActual = $deviceFiles -> getFileSize ( $path );
$fileHash = $deviceFiles -> getFileHash ( $path );
2021-07-07 22:07:11 +12:00
2021-11-30 21:04:19 +13:00
if ( $bucket -> getAttribute ( 'encryption' , true ) && $fileSize <= 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 );
2021-11-07 19:35:49 +13:00
}
2021-11-15 00:08:52 +13:00
try {
if ( $file -> isEmpty ()) {
$doc = new Document ([
2022-08-15 02:22:38 +12:00
'$id' => $fileId ,
2022-08-02 21:19:15 +12:00
'$permissions' => $permissions ,
2022-08-15 23:24:31 +12:00
'bucketId' => $bucket -> getId (),
2021-11-15 00:08:52 +13:00
'name' => $fileName ,
'path' => $path ,
'signature' => $fileHash ,
'mimeType' => $mimeType ,
2021-11-30 21:04:19 +13:00
'sizeOriginal' => $fileSize ,
2021-11-15 00:08:52 +13:00
'sizeActual' => $sizeActual ,
'algorithm' => $algorithm ,
'comment' => '' ,
'chunksTotal' => $chunks ,
'chunksUploaded' => $chunksUploaded ,
'openSSLVersion' => $openSSLVersion ,
'openSSLCipher' => $openSSLCipher ,
'openSSLTag' => $openSSLTag ,
'openSSLIV' => $openSSLIV ,
2022-01-16 20:55:55 +13:00
'search' => implode ( ' ' , [ $fileId , $fileName ]),
2021-11-25 17:47:40 +13:00
'metadata' => $metadata ,
2021-11-15 00:08:52 +13:00
]);
2022-08-08 22:58:36 +12:00
2022-08-15 19:20:10 +12:00
$file = $dbForProject -> createDocument ( 'bucket_' . $bucket -> getInternalId (), $doc );
2021-11-15 00:08:52 +13:00
} else {
$file = $file
2022-08-02 21:19:15 +12:00
-> setAttribute ( '$permissions' , $permissions )
2021-11-15 01:23:45 +13:00
-> setAttribute ( 'signature' , $fileHash )
-> setAttribute ( 'mimeType' , $mimeType )
-> setAttribute ( 'sizeActual' , $sizeActual )
-> setAttribute ( 'algorithm' , $algorithm )
-> setAttribute ( 'openSSLVersion' , $openSSLVersion )
-> setAttribute ( 'openSSLCipher' , $openSSLCipher )
-> setAttribute ( 'openSSLTag' , $openSSLTag )
2021-11-25 17:47:40 +13:00
-> setAttribute ( 'openSSLIV' , $openSSLIV )
2021-12-10 23:37:35 +13:00
-> setAttribute ( 'metadata' , $metadata )
2021-12-09 20:45:52 +13:00
-> setAttribute ( 'chunksUploaded' , $chunksUploaded );
2021-11-15 00:08:52 +13:00
2022-08-15 19:20:10 +12:00
$file = $dbForProject -> updateDocument ( 'bucket_' . $bucket -> getInternalId (), $fileId , $file );
2021-11-15 00:08:52 +13:00
}
2022-08-25 23:30:26 +12:00
} catch ( AuthorizationException ) {
throw new Exception ( Exception :: USER_UNAUTHORIZED );
2022-01-16 20:55:55 +13:00
} catch ( StructureException $exception ) {
2022-07-27 02:24:32 +12:00
throw new Exception ( Exception :: DOCUMENT_INVALID_STRUCTURE , $exception -> getMessage ());
2022-08-08 22:58:36 +12:00
} catch ( DuplicateException ) {
2022-07-27 02:24:32 +12:00
throw new Exception ( Exception :: DOCUMENT_ALREADY_EXISTS );
2021-07-07 22:07:11 +12:00
}
} else {
2021-11-15 00:08:52 +13:00
try {
if ( $file -> isEmpty ()) {
$doc = new Document ([
2022-08-14 22:33:36 +12:00
'$id' => ID :: custom ( $fileId ),
2022-08-02 21:19:15 +12:00
'$permissions' => $permissions ,
2021-11-15 00:08:52 +13:00
'bucketId' => $bucket -> getId (),
'name' => $fileName ,
'path' => $path ,
'signature' => '' ,
'mimeType' => '' ,
2021-11-30 21:04:19 +13:00
'sizeOriginal' => $fileSize ,
2021-11-15 00:08:52 +13:00
'sizeActual' => 0 ,
'algorithm' => '' ,
'comment' => '' ,
'chunksTotal' => $chunks ,
'chunksUploaded' => $chunksUploaded ,
2022-01-16 20:55:55 +13:00
'search' => implode ( ' ' , [ $fileId , $fileName ]),
2021-11-25 17:47:40 +13:00
'metadata' => $metadata ,
2021-11-15 00:08:52 +13:00
]);
2022-08-08 22:58:36 +12:00
2022-08-15 19:20:10 +12:00
$file = $dbForProject -> createDocument ( 'bucket_' . $bucket -> getInternalId (), $doc );
2021-11-15 00:08:52 +13:00
} else {
$file = $file
2021-11-25 17:47:40 +13:00
-> setAttribute ( 'chunksUploaded' , $chunksUploaded )
-> setAttribute ( 'metadata' , $metadata );
2021-11-15 00:08:52 +13:00
2022-08-15 19:20:10 +12:00
$file = $dbForProject -> updateDocument ( 'bucket_' . $bucket -> getInternalId (), $fileId , $file );
2021-11-15 00:08:52 +13:00
}
2022-08-25 23:30:26 +12:00
} catch ( AuthorizationException ) {
throw new Exception ( Exception :: USER_UNAUTHORIZED );
2022-01-16 20:55:55 +13:00
} catch ( StructureException $exception ) {
2022-07-27 02:24:32 +12:00
throw new Exception ( Exception :: DOCUMENT_INVALID_STRUCTURE , $exception -> getMessage ());
2022-08-08 22:58:36 +12:00
} catch ( DuplicateException ) {
2022-07-27 02:24:32 +12:00
throw new Exception ( Exception :: DOCUMENT_ALREADY_EXISTS );
2021-07-07 22:07:11 +12:00
}
2021-11-07 19:35:49 +13:00
}
2021-06-17 22:10:58 +12:00
2022-04-14 00:39:31 +12:00
$events
-> setParam ( 'bucketId' , $bucket -> getId ())
-> setParam ( 'fileId' , $file -> getId ())
2022-06-22 22:51:49 +12:00
-> setContext ( 'bucket' , $bucket )
2022-04-14 00:39:31 +12:00
;
2022-02-16 14:30:27 +13:00
2022-08-16 00:16:32 +12:00
$deletes
-> setType ( DELETE_TYPE_CACHE_BY_RESOURCE )
-> setResource ( 'file/' . $file -> getId ())
;
2021-11-25 17:47:40 +13:00
$metadata = null ; // was causing leaks as it was passed by reference
2020-06-30 09:43:34 +12:00
2022-09-07 23:02:36 +12:00
$response
-> setStatusCode ( Response :: STATUS_CODE_CREATED )
-> dynamic ( $file , Response :: MODEL_FILE );
2020-12-27 05:48:36 +13:00
});
2020-02-01 11:34:07 +13:00
2021-06-18 21:24:16 +12:00
App :: get ( '/v1/storage/buckets/:bucketId/files' )
2021-07-05 20:07:45 +12:00
-> alias ( '/v1/storage/files' , [ 'bucketId' => 'default' ])
2019-05-09 18:54:39 +12:00
-> desc ( 'List Files' )
2020-06-26 06:32:12 +12:00
-> groups ([ 'api' , 'storage' ])
2019-06-09 01:13:19 +12:00
-> label ( 'scope' , 'files.read' )
2021-04-16 19:22:17 +12:00
-> label ( 'sdk.auth' , [ APP_AUTH_TYPE_SESSION , APP_AUTH_TYPE_KEY , APP_AUTH_TYPE_JWT ])
2022-08-11 14:18:22 +12:00
-> label ( 'usage.metric' , 'files.{scope}.requests.read' )
2022-08-21 14:03:19 +12:00
-> label ( 'usage.params' , [ 'bucketId:{request.bucketId}' ])
2019-05-09 18:54:39 +12:00
-> label ( 'sdk.namespace' , 'storage' )
2020-01-31 09:58:49 +13:00
-> label ( 'sdk.method' , 'listFiles' )
2019-10-08 20:09:35 +13:00
-> label ( 'sdk.description' , '/docs/references/storage/list-files.md' )
2020-11-12 10:02:24 +13:00
-> label ( 'sdk.response.code' , Response :: STATUS_CODE_OK )
-> label ( 'sdk.response.type' , Response :: CONTENT_TYPE_JSON )
-> label ( 'sdk.response.model' , Response :: MODEL_FILE_LIST )
2022-09-19 22:05:42 +12:00
-> param ( 'bucketId' , '' , new UID (), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).' )
2022-08-23 20:49:39 +12:00
-> param ( 'queries' , [], new Files (), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode ( ', ' , Files :: ALLOWED_ATTRIBUTES ), true )
2020-09-11 02:40:14 +12:00
-> param ( 'search' , '' , new Text ( 256 ), 'Search term to filter your list results. Max length: 256 chars.' , true )
2020-12-27 05:48:36 +13:00
-> inject ( 'response' )
2021-12-28 01:45:23 +13:00
-> inject ( 'dbForProject' )
2021-11-19 22:45:42 +13:00
-> inject ( 'mode' )
2022-08-25 21:59:28 +12:00
-> action ( function ( string $bucketId , array $queries , string $search , Response $response , Database $dbForProject , string $mode ) {
2021-06-18 21:24:16 +12:00
2022-03-15 22:51:51 +13:00
$bucket = Authorization :: skip ( fn () => $dbForProject -> getDocument ( 'buckets' , $bucketId ));
2021-06-20 22:55:24 +12:00
2022-08-08 22:58:36 +12:00
if ( $bucket -> isEmpty () || ( ! $bucket -> getAttribute ( 'enabled' ) && $mode !== APP_MODE_ADMIN )) {
2022-07-27 02:24:32 +12:00
throw new Exception ( Exception :: STORAGE_BUCKET_NOT_FOUND );
2021-06-20 22:55:24 +12:00
}
2022-08-08 22:58:36 +12:00
2022-08-25 01:24:54 +12:00
$fileSecurity = $bucket -> getAttribute ( 'fileSecurity' , false );
$validator = new Authorization ( Database :: PERMISSION_READ );
$valid = $validator -> isValid ( $bucket -> getRead ());
if ( ! $fileSecurity && ! $valid ) {
2022-08-16 20:30:00 +12:00
throw new Exception ( Exception :: USER_UNAUTHORIZED );
2021-11-07 19:35:49 +13:00
}
2022-08-23 20:49:39 +12:00
$queries = Query :: parseQueries ( $queries );
2021-06-18 21:24:16 +12:00
2022-08-12 11:53:52 +12:00
if ( ! empty ( $search )) {
2022-08-23 20:49:39 +12:00
$queries [] = Query :: search ( 'search' , $search );
2021-06-18 21:24:16 +12:00
}
2021-05-03 20:28:31 +12:00
2022-08-23 20:49:39 +12:00
// Get cursor document if there was a cursor query
2022-08-31 11:31:43 +12:00
$cursor = Query :: getByType ( $queries , Query :: TYPE_CURSORAFTER , Query :: TYPE_CURSORBEFORE );
$cursor = reset ( $cursor );
2022-08-30 23:55:23 +12:00
if ( $cursor ) {
2022-08-23 20:49:39 +12:00
/** @var Query $cursor */
$fileId = $cursor -> getValue ();
2022-08-25 20:56:06 +12:00
2022-08-25 13:05:31 +12:00
if ( $fileSecurity && ! $valid ) {
2022-08-25 20:56:06 +12:00
$cursorDocument = $dbForProject -> getDocument ( 'bucket_' . $bucket -> getInternalId (), $fileId );
2022-08-25 13:05:31 +12:00
} else {
2022-08-25 20:56:06 +12:00
$cursorDocument = Authorization :: skip ( fn () => $dbForProject -> getDocument ( 'bucket_' . $bucket -> getInternalId (), $fileId ));
2022-08-25 13:05:31 +12:00
}
2021-08-07 00:36:27 +12:00
2022-08-12 11:53:52 +12:00
if ( $cursorDocument -> isEmpty ()) {
2022-08-23 20:49:39 +12:00
throw new Exception ( Exception :: GENERAL_CURSOR_NOT_FOUND , " File ' { $fileId } ' for the 'cursor' value not found. " );
2021-08-07 00:36:27 +12:00
}
2021-08-19 01:42:03 +12:00
2022-08-23 20:49:39 +12:00
$cursor -> setValue ( $cursorDocument );
2021-08-19 01:42:03 +12:00
}
2022-08-08 22:58:36 +12:00
2022-08-23 20:49:39 +12:00
$filterQueries = Query :: groupByType ( $queries )[ 'filters' ];
2022-08-25 01:24:54 +12:00
if ( $fileSecurity && ! $valid ) {
2022-08-23 20:49:39 +12:00
$files = $dbForProject -> find ( 'bucket_' . $bucket -> getInternalId (), $queries );
2022-08-12 11:53:52 +12:00
$total = $dbForProject -> count ( 'bucket_' . $bucket -> getInternalId (), $filterQueries , APP_LIMIT_COUNT );
2022-08-08 22:58:36 +12:00
} else {
2022-08-23 20:49:39 +12:00
$files = Authorization :: skip ( fn () => $dbForProject -> find ( 'bucket_' . $bucket -> getInternalId (), $queries ));
2022-08-14 23:46:34 +12:00
$total = Authorization :: skip ( fn () => $dbForProject -> count ( 'bucket_' . $bucket -> getInternalId (), $filterQueries , APP_LIMIT_COUNT ));
2022-08-08 22:58:36 +12:00
}
2021-11-07 19:35:49 +13:00
2020-10-31 08:53:27 +13:00
$response -> dynamic ( new Document ([
2021-11-07 19:35:49 +13:00
'files' => $files ,
2022-08-12 11:53:52 +12:00
'total' => $total ,
2020-10-31 08:53:27 +13:00
]), Response :: MODEL_FILE_LIST );
2020-12-27 05:48:36 +13:00
});
2019-05-09 18:54:39 +12:00
2021-06-18 21:33:00 +12:00
App :: get ( '/v1/storage/buckets/:bucketId/files/:fileId' )
2021-07-05 20:07:45 +12:00
-> alias ( '/v1/storage/files/:fileId' , [ 'bucketId' => 'default' ])
2019-05-09 18:54:39 +12:00
-> desc ( 'Get File' )
2020-06-26 06:32:12 +12:00
-> groups ([ 'api' , 'storage' ])
2019-06-09 01:13:19 +12:00
-> label ( 'scope' , 'files.read' )
2021-04-16 19:22:17 +12:00
-> label ( 'sdk.auth' , [ APP_AUTH_TYPE_SESSION , APP_AUTH_TYPE_KEY , APP_AUTH_TYPE_JWT ])
2022-08-11 14:18:22 +12:00
-> label ( 'usage.metric' , 'files.{scope}.requests.read' )
2022-08-21 14:03:19 +12:00
-> label ( 'usage.params' , [ 'bucketId:{request.bucketId}' ])
2019-05-09 18:54:39 +12:00
-> label ( 'sdk.namespace' , 'storage' )
2020-01-31 09:58:49 +13:00
-> label ( 'sdk.method' , 'getFile' )
2019-10-08 20:09:35 +13:00
-> label ( 'sdk.description' , '/docs/references/storage/get-file.md' )
2020-11-12 10:02:24 +13:00
-> label ( 'sdk.response.code' , Response :: STATUS_CODE_OK )
-> label ( 'sdk.response.type' , Response :: CONTENT_TYPE_JSON )
-> label ( 'sdk.response.model' , Response :: MODEL_FILE )
2022-09-19 22:05:42 +12:00
-> param ( 'bucketId' , '' , new UID (), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).' )
2021-12-11 01:27:11 +13:00
-> param ( 'fileId' , '' , new UID (), 'File ID.' )
2020-12-27 05:48:36 +13:00
-> inject ( 'response' )
2021-12-28 01:45:23 +13:00
-> inject ( 'dbForProject' )
2021-11-19 22:45:42 +13:00
-> inject ( 'mode' )
2022-08-11 14:18:22 +12:00
-> action ( function ( string $bucketId , string $fileId , Response $response , Database $dbForProject , string $mode ) {
2019-05-09 18:54:39 +12:00
2022-03-15 22:51:51 +13:00
$bucket = Authorization :: skip ( fn () => $dbForProject -> getDocument ( 'buckets' , $bucketId ));
2019-05-09 18:54:39 +12:00
2022-08-08 22:58:36 +12:00
if ( $bucket -> isEmpty () || ( ! $bucket -> getAttribute ( 'enabled' ) && $mode !== APP_MODE_ADMIN )) {
2022-07-27 02:24:32 +12:00
throw new Exception ( Exception :: STORAGE_BUCKET_NOT_FOUND );
2021-06-20 22:55:24 +12:00
}
2022-08-08 22:58:36 +12:00
$fileSecurity = $bucket -> getAttribute ( 'fileSecurity' , false );
2022-08-25 01:24:54 +12:00
$validator = new Authorization ( Database :: PERMISSION_READ );
2022-08-08 22:58:36 +12:00
$valid = $validator -> isValid ( $bucket -> getRead ());
2022-08-26 14:52:25 +12:00
if ( ! $fileSecurity && ! $valid ) {
2022-08-16 20:30:00 +12:00
throw new Exception ( Exception :: USER_UNAUTHORIZED );
2021-11-07 19:35:49 +13:00
}
2022-08-08 22:58:36 +12:00
2022-08-25 01:24:54 +12:00
if ( $fileSecurity && ! $valid ) {
2022-08-26 14:23:23 +12:00
$file = $dbForProject -> getDocument ( 'bucket_' . $bucket -> getInternalId (), $fileId );
2022-08-25 01:24:54 +12:00
} else {
$file = Authorization :: skip ( fn () => $dbForProject -> getDocument ( 'bucket_' . $bucket -> getInternalId (), $fileId ));
}
2021-06-18 21:33:00 +12:00
2022-08-26 15:01:16 +12:00
if ( $file -> isEmpty ()) {
2022-07-27 02:24:32 +12:00
throw new Exception ( Exception :: STORAGE_FILE_NOT_FOUND );
2019-05-09 18:54:39 +12:00
}
2022-03-15 22:51:51 +13:00
2020-10-31 08:53:27 +13:00
$response -> dynamic ( $file , Response :: MODEL_FILE );
2020-12-27 05:48:36 +13:00
});
2019-05-09 18:54:39 +12:00
2021-06-20 23:20:35 +12:00
App :: get ( '/v1/storage/buckets/:bucketId/files/:fileId/preview' )
2021-07-05 20:07:45 +12:00
-> alias ( '/v1/storage/files/:fileId/preview' , [ 'bucketId' => 'default' ])
2019-08-25 20:10:28 +12:00
-> desc ( 'Get File Preview' )
2020-06-26 06:32:12 +12:00
-> groups ([ 'api' , 'storage' ])
2019-06-09 01:13:19 +12:00
-> label ( 'scope' , 'files.read' )
2022-08-15 21:05:41 +12:00
-> label ( 'cache' , true )
2022-11-10 23:08:01 +13:00
-> label ( 'cache.resourceType' , 'bucket/{request.bucketId}' )
2022-08-15 21:05:41 +12:00
-> label ( 'cache.resource' , 'file/{request.fileId}' )
2022-08-11 14:18:22 +12:00
-> label ( 'usage.metric' , 'files.{scope}.requests.read' )
2022-08-21 14:03:19 +12:00
-> label ( 'usage.params' , [ 'bucketId:{request.bucketId}' ])
2021-04-16 19:22:17 +12:00
-> label ( 'sdk.auth' , [ APP_AUTH_TYPE_SESSION , APP_AUTH_TYPE_KEY , APP_AUTH_TYPE_JWT ])
2019-05-09 18:54:39 +12:00
-> label ( 'sdk.namespace' , 'storage' )
2020-01-31 09:58:49 +13:00
-> label ( 'sdk.method' , 'getFilePreview' )
2019-10-08 20:09:35 +13:00
-> label ( 'sdk.description' , '/docs/references/storage/get-file-preview.md' )
2020-11-12 10:02:24 +13:00
-> label ( 'sdk.response.code' , Response :: STATUS_CODE_OK )
2020-11-12 11:02:02 +13:00
-> label ( 'sdk.response.type' , Response :: CONTENT_TYPE_IMAGE )
2022-02-27 20:39:33 +13:00
-> label ( 'sdk.methodType' , 'location' )
2022-09-19 22:05:42 +12:00
-> param ( 'bucketId' , '' , new UID (), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).' )
2021-12-16 19:02:51 +13:00
-> param ( 'fileId' , '' , new UID (), 'File ID' )
2020-09-11 02:40:14 +12:00
-> 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-06-24 18:30:52 +12:00
-> param ( 'gravity' , Image :: GRAVITY_CENTER , new WhiteList ( Image :: getGravityTypes ()), 'Image crop gravity. Can be one of ' . implode ( " , " , Image :: getGravityTypes ()), true )
2020-09-11 02:40:14 +12:00
-> param ( 'quality' , 100 , new Range ( 0 , 100 ), 'Preview image quality. Pass an integer between 0 to 100. Defaults to 100.' , true )
2021-04-04 20:40:03 +12:00
-> param ( 'borderWidth' , 0 , new Range ( 0 , 100 ), 'Preview image border in pixels. Pass an integer between 0 to 100. Defaults to 0.' , true )
2021-03-22 19:54:42 +13:00
-> param ( 'borderColor' , '' , new HexColor (), 'Preview image border color. Use a valid HEX color, no # is needed for prefix.' , true )
2021-04-04 20:40:03 +12:00
-> param ( 'borderRadius' , 0 , new Range ( 0 , 4000 ), 'Preview image border radius in pixels. Pass an integer between 0 to 4000.' , true )
2022-01-16 20:55:55 +13: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 ( - 360 , 360 ), 'Preview image rotation in degrees. Pass an integer between -360 and 360.' , true )
2020-09-11 02:40:14 +12:00
-> 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 )
2020-12-27 05:48:36 +13:00
-> inject ( 'request' )
-> inject ( 'response' )
-> inject ( 'project' )
2021-12-28 01:45:23 +13:00
-> inject ( 'dbForProject' )
2021-11-19 22:45:42 +13:00
-> inject ( 'mode' )
2021-12-13 21:08:27 +13:00
-> inject ( 'deviceFiles' )
2022-03-02 00:37:51 +13:00
-> inject ( 'deviceLocal' )
2022-08-11 14:18:22 +12:00
-> action ( function ( string $bucketId , string $fileId , int $width , int $height , string $gravity , int $quality , int $borderWidth , string $borderColor , int $borderRadius , float $opacity , int $rotation , string $background , string $output , Request $request , Response $response , Document $project , Database $dbForProject , string $mode , Device $deviceFiles , Device $deviceLocal ) {
2019-05-09 18:54:39 +12:00
2020-06-30 23:09:28 +12:00
if ( ! \extension_loaded ( 'imagick' )) {
2022-07-27 02:24:32 +12:00
throw new Exception ( Exception :: GENERAL_SERVER_ERROR , 'Imagick extension is missing' );
2020-06-30 23:09:28 +12:00
}
2019-05-09 18:54:39 +12:00
2022-03-15 22:51:51 +13:00
$bucket = Authorization :: skip ( fn () => $dbForProject -> getDocument ( 'buckets' , $bucketId ));
2021-06-21 20:05:11 +12:00
2022-08-08 22:58:36 +12:00
if ( $bucket -> isEmpty () || ( ! $bucket -> getAttribute ( 'enabled' ) && $mode !== APP_MODE_ADMIN )) {
2022-07-27 02:24:32 +12:00
throw new Exception ( Exception :: STORAGE_BUCKET_NOT_FOUND );
2020-06-30 23:09:28 +12:00
}
2022-08-08 22:58:36 +12:00
$fileSecurity = $bucket -> getAttribute ( 'fileSecurity' , false );
2022-08-25 01:24:54 +12:00
$validator = new Authorization ( Database :: PERMISSION_READ );
2022-08-08 22:58:36 +12:00
$valid = $validator -> isValid ( $bucket -> getRead ());
2022-08-25 01:24:54 +12:00
if ( ! $fileSecurity && ! $valid ) {
2022-08-16 20:30:00 +12:00
throw new Exception ( Exception :: USER_UNAUTHORIZED );
2021-11-07 19:35:49 +13:00
}
2021-11-07 18:50:21 +13:00
if (( \strpos ( $request -> getAccept (), 'image/webp' ) === false ) && ( 'webp' === $output )) { // Fallback webp to jpeg when no browser support
2020-06-30 23:09:28 +12:00
$output = 'jpg' ;
}
2019-05-09 18:54:39 +12:00
2020-06-30 23:09:28 +12:00
$inputs = Config :: getParam ( 'storage-inputs' );
$outputs = Config :: getParam ( 'storage-outputs' );
$fileLogos = Config :: getParam ( 'storage-logos' );
2020-06-30 16:32:36 +12:00
2022-08-25 01:24:54 +12:00
if ( $fileSecurity && ! $valid ) {
2022-08-26 14:23:23 +12:00
$file = $dbForProject -> getDocument ( 'bucket_' . $bucket -> getInternalId (), $fileId );
2022-08-25 01:24:54 +12:00
} else {
$file = Authorization :: skip ( fn () => $dbForProject -> getDocument ( 'bucket_' . $bucket -> getInternalId (), $fileId ));
}
2019-05-09 18:54:39 +12:00
2022-08-26 15:01:16 +12:00
if ( $file -> isEmpty ()) {
2022-07-27 02:24:32 +12:00
throw new Exception ( Exception :: STORAGE_FILE_NOT_FOUND );
2020-06-30 23:09:28 +12:00
}
2019-05-09 18:54:39 +12:00
2020-06-30 23:09:28 +12:00
$path = $file -> getAttribute ( 'path' );
$type = \strtolower ( \pathinfo ( $path , PATHINFO_EXTENSION ));
2022-08-31 01:46:55 +12:00
$algorithm = $file -> getAttribute ( 'algorithm' , 'none' );
2021-05-03 20:28:31 +12:00
$cipher = $file -> getAttribute ( 'openSSLCipher' );
2020-06-30 23:09:28 +12:00
$mime = $file -> getAttribute ( 'mimeType' );
2022-03-20 15:35:52 +13:00
if ( ! \in_array ( $mime , $inputs ) || $file -> getAttribute ( 'sizeActual' ) > ( int ) App :: getEnv ( '_APP_STORAGE_PREVIEW_LIMIT' , 20000000 )) {
2022-05-24 02:54:50 +12:00
if ( ! \in_array ( $mime , $inputs )) {
2022-02-16 23:04:49 +13:00
$path = ( \array_key_exists ( $mime , $fileLogos )) ? $fileLogos [ $mime ] : $fileLogos [ 'default' ];
} else {
// it was an image but the file size exceeded the limit
$path = $fileLogos [ 'default_image' ];
}
2022-08-31 01:46:55 +12:00
$algorithm = 'none' ;
2020-06-30 23:09:28 +12:00
$cipher = null ;
$background = ( empty ( $background )) ? 'eceff1' : $background ;
2020-06-20 23:20:49 +12:00
$type = \strtolower ( \pathinfo ( $path , PATHINFO_EXTENSION ));
2022-03-02 00:37:51 +13:00
$deviceFiles = $deviceLocal ;
2020-06-30 23:09:28 +12:00
}
2019-05-12 16:56:55 +12:00
2021-12-13 21:08:27 +13:00
if ( ! $deviceFiles -> exists ( $path )) {
2022-07-27 02:24:32 +12:00
throw new Exception ( Exception :: STORAGE_FILE_NOT_FOUND );
2020-06-30 23:09:28 +12:00
}
2019-05-09 18:54:39 +12:00
2022-05-24 02:54:50 +12:00
if ( empty ( $output )) {
2022-03-20 15:48:40 +13:00
// when file extension is not provided and the mime type is not one of our supported outputs
// we fallback to `jpg` output format
2022-03-19 00:51:59 +13:00
$output = empty ( $type ) ? ( array_search ( $mime , $outputs ) ? ? 'jpg' ) : $type ;
2022-03-19 00:07:54 +13:00
}
2019-05-09 18:54:39 +12:00
2021-12-13 21:08:27 +13:00
$source = $deviceFiles -> read ( $path );
2020-06-30 23:09:28 +12:00
if ( ! empty ( $cipher )) { // Decrypt
$source = OpenSSL :: decrypt (
$source ,
2021-05-03 20:28:31 +12:00
$file -> getAttribute ( 'openSSLCipher' ),
2021-07-07 22:07:11 +12:00
App :: getEnv ( '_APP_OPENSSL_KEY_V' . $file -> getAttribute ( 'openSSLVersion' )),
2020-06-30 23:09:28 +12:00
0 ,
2021-05-03 20:28:31 +12:00
\hex2bin ( $file -> getAttribute ( 'openSSLIV' )),
\hex2bin ( $file -> getAttribute ( 'openSSLTag' ))
2020-06-30 23:09:28 +12:00
);
}
2019-05-09 18:54:39 +12:00
2022-08-31 14:56:44 +12:00
switch ( $algorithm ) {
2022-08-31 01:46:55 +12:00
case 'zstd' :
$compressor = new Zstd ();
$source = $compressor -> decompress ( $source );
break ;
case 'gzip' :
$compressor = new GZIP ();
$source = $compressor -> decompress ( $source );
break ;
2020-06-30 23:09:28 +12:00
}
2019-05-09 18:54:39 +12:00
2021-02-20 02:59:46 +13:00
$image = new Image ( $source );
2019-05-09 18:54:39 +12:00
2021-06-12 00:26:04 +12:00
$image -> crop (( int ) $width , ( int ) $height , $gravity );
2022-01-16 20:55:55 +13:00
if ( ! empty ( $opacity ) || $opacity === 0 ) {
2021-04-09 19:42:45 +12:00
$image -> setOpacity ( $opacity );
}
2019-05-09 18:54:39 +12:00
2020-06-30 23:09:28 +12:00
if ( ! empty ( $background )) {
2021-07-07 22:07:11 +12:00
$image -> setBackground ( '#' . $background );
2020-06-30 23:09:28 +12:00
}
2019-05-09 18:54:39 +12:00
2021-07-07 22:07:11 +12:00
if ( ! empty ( $borderWidth )) {
$image -> setBorder ( $borderWidth , '#' . $borderColor );
2021-04-09 19:04:46 +12:00
}
2019-05-09 18:54:39 +12:00
2021-03-22 19:54:42 +13:00
if ( ! empty ( $borderRadius )) {
$image -> setBorderRadius ( $borderRadius );
}
if ( ! empty ( $rotation )) {
2021-11-12 03:07:37 +13:00
$image -> setRotation (( $rotation + 360 ) % 360 );
2021-03-22 19:54:42 +13:00
}
2021-02-20 02:59:46 +13:00
$data = $image -> output ( $output , $quality );
2020-07-03 15:11:16 +12:00
2022-07-24 21:49:51 +12:00
$contentType = ( \array_key_exists ( $output , $outputs )) ? $outputs [ $output ] : $outputs [ 'jpg' ];
2022-07-25 03:22:31 +12:00
2020-06-30 23:09:28 +12:00
$response
2022-07-29 03:24:18 +12:00
-> addHeader ( 'Expires' , \date ( 'D, d M Y H:i:s' , \time () + 60 * 60 * 24 * 30 ) . ' GMT' )
2022-07-24 21:49:51 +12:00
-> setContentType ( $contentType )
2022-08-15 03:01:34 +12:00
-> file ( $data )
2020-06-30 23:09:28 +12:00
;
2019-05-09 18:54:39 +12:00
2021-02-20 02:59:46 +13:00
unset ( $image );
2020-12-27 05:48:36 +13:00
});
2019-05-09 18:54:39 +12:00
2021-06-20 23:20:35 +12:00
App :: get ( '/v1/storage/buckets/:bucketId/files/:fileId/download' )
2021-07-05 20:07:45 +12:00
-> alias ( '/v1/storage/files/:fileId/download' , [ 'bucketId' => 'default' ])
2019-08-25 20:10:28 +12:00
-> desc ( 'Get File for Download' )
2020-06-26 06:32:12 +12:00
-> groups ([ 'api' , 'storage' ])
2019-06-09 01:13:19 +12:00
-> label ( 'scope' , 'files.read' )
2022-08-11 14:18:22 +12:00
-> label ( 'usage.metric' , 'files.{scope}.requests.read' )
2022-08-21 14:03:19 +12:00
-> label ( 'usage.params' , [ 'bucketId:{request.bucketId}' ])
2021-04-16 19:22:17 +12:00
-> label ( 'sdk.auth' , [ APP_AUTH_TYPE_SESSION , APP_AUTH_TYPE_KEY , APP_AUTH_TYPE_JWT ])
2019-05-09 18:54:39 +12:00
-> label ( 'sdk.namespace' , 'storage' )
2020-01-31 09:58:49 +13:00
-> label ( 'sdk.method' , 'getFileDownload' )
2019-10-08 20:09:35 +13:00
-> label ( 'sdk.description' , '/docs/references/storage/get-file-download.md' )
2020-11-12 10:02:24 +13:00
-> label ( 'sdk.response.code' , Response :: STATUS_CODE_OK )
2020-11-12 18:12:25 +13:00
-> label ( 'sdk.response.type' , '*/*' )
2020-04-11 06:59:14 +12:00
-> label ( 'sdk.methodType' , 'location' )
2022-09-19 22:05:42 +12:00
-> param ( 'bucketId' , '' , new UID (), 'Storage bucket ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).' )
2021-12-11 01:27:11 +13:00
-> param ( 'fileId' , '' , new UID (), 'File ID.' )
2021-09-12 19:03:25 +12:00
-> inject ( 'request' )
2020-12-27 05:48:36 +13:00
-> inject ( 'response' )
2021-12-28 01:45:23 +13:00
-> inject ( 'dbForProject' )
2021-11-19 22:45:42 +13:00
-> inject ( 'mode' )
2021-12-13 21:08:27 +13:00
-> inject ( 'deviceFiles' )
2022-08-11 14:18:22 +12:00
-> action ( function ( string $bucketId , string $fileId , Request $request , Response $response , Database $dbForProject , string $mode , Device $deviceFiles ) {
2019-05-09 18:54:39 +12:00
2022-03-15 22:51:51 +13:00
$bucket = Authorization :: skip ( fn () => $dbForProject -> getDocument ( 'buckets' , $bucketId ));
2019-05-09 18:54:39 +12:00
2022-08-08 22:58:36 +12:00
if ( $bucket -> isEmpty () || ( ! $bucket -> getAttribute ( 'enabled' ) && $mode !== APP_MODE_ADMIN )) {
2022-07-27 02:24:32 +12:00
throw new Exception ( Exception :: STORAGE_BUCKET_NOT_FOUND );
2021-06-20 23:20:35 +12:00
}
2022-08-08 22:58:36 +12:00
$fileSecurity = $bucket -> getAttribute ( 'fileSecurity' , false );
2022-08-25 01:24:54 +12:00
$validator = new Authorization ( Database :: PERMISSION_READ );
2022-08-08 22:58:36 +12:00
$valid = $validator -> isValid ( $bucket -> getRead ());
2022-08-25 01:24:54 +12:00
if ( ! $fileSecurity && ! $valid ) {
2022-08-16 20:30:00 +12:00
throw new Exception ( Exception :: USER_UNAUTHORIZED );
2021-11-07 19:35:49 +13:00
}
2022-08-25 01:24:54 +12:00
if ( $fileSecurity && ! $valid ) {
2022-08-26 14:23:23 +12:00
$file = $dbForProject -> getDocument ( 'bucket_' . $bucket -> getInternalId (), $fileId );
2022-08-25 01:24:54 +12:00
} else {
$file = Authorization :: skip ( fn () => $dbForProject -> getDocument ( 'bucket_' . $bucket -> getInternalId (), $fileId ));
}
2021-06-20 23:20:35 +12:00
2022-08-26 15:01:16 +12:00
if ( $file -> isEmpty ()) {
2022-07-27 02:24:32 +12:00
throw new Exception ( Exception :: STORAGE_FILE_NOT_FOUND );
2020-06-30 23:09:28 +12:00
}
2019-05-09 18:54:39 +12:00
2020-06-30 23:09:28 +12:00
$path = $file -> getAttribute ( 'path' , '' );
2019-05-09 18:54:39 +12:00
2021-12-13 21:08:27 +13:00
if ( ! $deviceFiles -> exists ( $path )) {
2022-07-27 02:24:32 +12:00
throw new Exception ( Exception :: STORAGE_FILE_NOT_FOUND , 'File not found in ' . $path );
2020-06-30 23:09:28 +12:00
}
2019-05-09 18:54:39 +12:00
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' , '' ) . '"' )
;
2019-05-09 18:54:39 +12:00
2021-09-13 18:17:45 +12:00
$size = $file -> getAttribute ( 'sizeOriginal' , 0 );
2020-06-30 23:09:28 +12:00
2021-09-12 20:50:43 +12:00
$rangeHeader = $request -> getHeader ( 'range' );
2022-01-16 20:55:55 +13:00
if ( ! empty ( $rangeHeader )) {
2021-09-30 22:46:45 +13:00
$start = $request -> getRangeStart ();
$end = $request -> getRangeEnd ();
$unit = $request -> getRangeUnit ();
2021-12-09 23:44:44 +13:00
if ( $end === null ) {
2022-01-16 20:55:55 +13:00
$end = min (( $start + MAX_OUTPUT_CHUNK_SIZE - 1 ), ( $size - 1 ));
2021-09-30 22:46:45 +13:00
}
2021-12-09 23:44:44 +13:00
if ( $unit !== 'bytes' || $start >= $end || $end >= $size ) {
2022-07-27 02:24:32 +12:00
throw new Exception ( Exception :: STORAGE_INVALID_RANGE );
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-05-03 20:28:31 +12:00
if ( ! empty ( $file -> getAttribute ( 'openSSLCipher' ))) { // Decrypt
2021-12-13 21:08:27 +13:00
$source = $deviceFiles -> read ( $path );
2020-06-30 23:09:28 +12:00
$source = OpenSSL :: decrypt (
$source ,
2021-05-03 20:28:31 +12:00
$file -> getAttribute ( 'openSSLCipher' ),
2021-07-13 19:49:19 +12:00
App :: getEnv ( '_APP_OPENSSL_KEY_V' . $file -> getAttribute ( 'openSSLVersion' )),
2020-06-30 23:09:28 +12:00
0 ,
2021-05-03 20:28:31 +12:00
\hex2bin ( $file -> getAttribute ( 'openSSLIV' )),
\hex2bin ( $file -> getAttribute ( 'openSSLTag' ))
2020-06-30 23:09:28 +12:00
);
}
2019-05-09 18:54:39 +12:00
2022-08-31 14:56:44 +12:00
switch ( $file -> getAttribute ( 'algorithm' , 'none' )) {
2022-08-31 01:46:55 +12:00
case 'zstd' :
if ( empty ( $source )) {
$source = $deviceFiles -> read ( $path );
}
$compressor = new Zstd ();
$source = $compressor -> decompress ( $source );
break ;
case 'gzip' :
if ( empty ( $source )) {
$source = $deviceFiles -> read ( $path );
}
$compressor = new GZIP ();
$source = $compressor -> decompress ( $source );
break ;
2021-06-22 20:36:44 +12:00
}
2021-06-20 23:20:35 +12:00
2021-12-09 23:44:44 +13:00
if ( ! empty ( $source )) {
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 );
}
2019-05-09 18:54:39 +12:00
2021-12-09 23:44:44 +13:00
if ( ! empty ( $rangeHeader )) {
2021-12-13 21:08:27 +13:00
$response -> send ( $deviceFiles -> read ( $path , $start , ( $end - $start + 1 )));
2021-09-12 20:50:43 +12:00
}
2021-08-16 19:25:20 +12:00
2022-01-16 20:55:55 +13:00
if ( $size > APP_STORAGE_READ_BUFFER ) {
2021-12-13 21:08:27 +13:00
$response -> addHeader ( 'Content-Length' , $deviceFiles -> getFileSize ( $path ));
2022-01-16 20:55:55 +13:00
for ( $i = 0 ; $i < ceil ( $size / MAX_OUTPUT_CHUNK_SIZE ); $i ++ ) {
2021-12-10 23:29:59 +13:00
$response -> chunk (
2021-12-13 21:08:27 +13:00
$deviceFiles -> read (
2021-12-10 23:29:59 +13:00
$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 {
2021-12-13 21:08:27 +13:00
$response -> send ( $deviceFiles -> read ( $path ));
2021-07-15 22:35:08 +12:00
}
2020-12-27 05:48:36 +13:00
});
2019-05-09 18:54:39 +12:00
2021-06-20 23:20:35 +12:00
App :: get ( '/v1/storage/buckets/:bucketId/files/:fileId/view' )
2021-07-05 20:07:45 +12:00
-> alias ( '/v1/storage/files/:fileId/view' , [ 'bucketId' => 'default' ])
2019-08-25 20:10:28 +12:00
-> desc ( 'Get File for View' )
2020-06-26 06:32:12 +12:00
-> groups ([ 'api' , 'storage' ])
2019-06-09 01:13:19 +12:00
-> label ( 'scope' , 'files.read' )
2022-08-11 14:18:22 +12:00
-> label ( 'usage.metric' , 'files.{scope}.requests.read' )
2022-08-21 14:03:19 +12:00
-> label ( 'usage.params' , [ 'bucketId:{request.bucketId}' ])
2021-04-16 19:22:17 +12:00
-> label ( 'sdk.auth' , [ APP_AUTH_TYPE_SESSION , APP_AUTH_TYPE_KEY , APP_AUTH_TYPE_JWT ])
2019-05-09 18:54:39 +12:00
-> label ( 'sdk.namespace' , 'storage' )
2020-01-31 09:58:49 +13:00
-> label ( 'sdk.method' , 'getFileView' )
2019-10-08 20:09:35 +13:00
-> label ( 'sdk.description' , '/docs/references/storage/get-file-view.md' )
2020-11-12 10:02:24 +13:00
-> label ( 'sdk.response.code' , Response :: STATUS_CODE_OK )
2020-11-12 18:12:25 +13:00
-> label ( 'sdk.response.type' , '*/*' )
2020-04-11 06:59:14 +12:00
-> label ( 'sdk.methodType' , 'location' )
2022-09-19 22:05:42 +12:00
-> param ( 'bucketId' , '' , new UID (), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).' )
2021-12-11 01:27:11 +13:00
-> param ( 'fileId' , '' , new UID (), 'File ID.' )
2020-12-27 05:48:36 +13:00
-> inject ( 'response' )
2021-09-13 18:49:35 +12:00
-> inject ( 'request' )
2021-12-28 01:45:23 +13:00
-> inject ( 'dbForProject' )
2021-11-19 22:45:42 +13:00
-> inject ( 'mode' )
2021-12-13 21:08:27 +13:00
-> inject ( 'deviceFiles' )
2022-08-11 14:18:22 +12:00
-> action ( function ( string $bucketId , string $fileId , Response $response , Request $request , Database $dbForProject , string $mode , Device $deviceFiles ) {
2021-06-20 23:20:35 +12:00
2022-03-15 22:51:51 +13:00
$bucket = Authorization :: skip ( fn () => $dbForProject -> getDocument ( 'buckets' , $bucketId ));
2021-06-20 23:20:35 +12:00
2022-08-08 22:58:36 +12:00
if ( $bucket -> isEmpty () || ( ! $bucket -> getAttribute ( 'enabled' ) && $mode !== APP_MODE_ADMIN )) {
2022-07-27 02:24:32 +12:00
throw new Exception ( Exception :: STORAGE_BUCKET_NOT_FOUND );
2021-06-20 23:20:35 +12:00
}
2022-08-08 22:58:36 +12:00
$fileSecurity = $bucket -> getAttribute ( 'fileSecurity' , false );
2022-08-25 01:24:54 +12:00
$validator = new Authorization ( Database :: PERMISSION_READ );
2022-08-08 22:58:36 +12:00
$valid = $validator -> isValid ( $bucket -> getRead ());
2022-08-25 01:24:54 +12:00
if ( ! $fileSecurity && ! $valid ) {
2022-08-16 20:30:00 +12:00
throw new Exception ( Exception :: USER_UNAUTHORIZED );
2021-11-07 19:35:49 +13:00
}
2019-05-09 18:54:39 +12:00
2022-08-25 01:24:54 +12:00
if ( $fileSecurity && ! $valid ) {
2022-08-26 14:23:23 +12:00
$file = $dbForProject -> getDocument ( 'bucket_' . $bucket -> getInternalId (), $fileId );
2022-08-25 01:24:54 +12:00
} else {
$file = Authorization :: skip ( fn () => $dbForProject -> getDocument ( 'bucket_' . $bucket -> getInternalId (), $fileId ));
}
2019-05-09 18:54:39 +12:00
2022-08-26 15:01:16 +12:00
if ( $file -> isEmpty ()) {
2022-07-27 02:24:32 +12:00
throw new Exception ( Exception :: STORAGE_FILE_NOT_FOUND );
2020-06-30 23:09:28 +12:00
}
2019-05-09 18:54:39 +12:00
2022-08-08 22:58:36 +12:00
$mimes = Config :: getParam ( 'storage-mimes' );
2020-06-30 23:09:28 +12:00
$path = $file -> getAttribute ( 'path' , '' );
2019-05-09 18:54:39 +12:00
2021-12-13 21:08:27 +13:00
if ( ! $deviceFiles -> exists ( $path )) {
2022-07-27 02:24:32 +12:00
throw new Exception ( Exception :: STORAGE_FILE_NOT_FOUND , 'File not found in ' . $path );
2020-06-30 23:09:28 +12:00
}
2019-05-09 18:54:39 +12:00
2020-06-30 23:09:28 +12:00
$contentType = 'text/plain' ;
2019-05-09 18:54:39 +12:00
2020-06-30 23:09:28 +12:00
if ( \in_array ( $file -> getAttribute ( 'mimeType' ), $mimes )) {
$contentType = $file -> getAttribute ( 'mimeType' );
}
2019-05-09 18:54:39 +12:00
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 ())
;
2021-06-20 23:20:35 +12:00
2021-09-13 18:49:35 +12:00
$size = $file -> getAttribute ( 'sizeOriginal' , 0 );
$rangeHeader = $request -> getHeader ( 'range' );
2022-01-16 20:55:55 +13:00
if ( ! empty ( $rangeHeader )) {
2021-09-30 22:47:53 +13:00
$start = $request -> getRangeStart ();
$end = $request -> getRangeEnd ();
$unit = $request -> getRangeUnit ();
2021-12-09 23:44:44 +13:00
if ( $end === null ) {
2022-01-16 20:55:55 +13:00
$end = min (( $start + 2000000 - 1 ), ( $size - 1 ));
2021-09-30 22:47:53 +13:00
}
2021-06-20 23:20:35 +12:00
2021-12-09 23:44:44 +13:00
if ( $unit != 'bytes' || $start >= $end || $end >= $size ) {
2022-07-27 02:24:32 +12:00
throw new Exception ( Exception :: STORAGE_INVALID_RANGE );
2021-09-13 18:49:35 +12:00
}
2020-06-30 23:09:28 +12:00
2021-09-30 22:47:53 +13:00
$response
-> addHeader ( 'Accept-Ranges' , 'bytes' )
2021-12-09 23:44:44 +13:00
-> addHeader ( 'Content-Range' , " bytes $start - $end / $size " )
2021-09-30 22:47:53 +13:00
-> addHeader ( 'Content-Length' , $end - $start + 1 )
-> setStatusCode ( Response :: STATUS_CODE_PARTIALCONTENT );
2021-09-13 18:49:35 +12:00
}
2021-07-15 22:35:08 +12:00
$source = '' ;
2021-05-03 20:28:31 +12:00
if ( ! empty ( $file -> getAttribute ( 'openSSLCipher' ))) { // Decrypt
2021-12-13 21:08:27 +13:00
$source = $deviceFiles -> read ( $path );
2020-06-30 23:09:28 +12:00
$source = OpenSSL :: decrypt (
$source ,
2021-05-03 20:28:31 +12:00
$file -> getAttribute ( 'openSSLCipher' ),
2021-07-13 19:49:19 +12:00
App :: getEnv ( '_APP_OPENSSL_KEY_V' . $file -> getAttribute ( 'openSSLVersion' )),
2020-06-30 23:09:28 +12:00
0 ,
2021-05-03 20:28:31 +12:00
\hex2bin ( $file -> getAttribute ( 'openSSLIV' )),
\hex2bin ( $file -> getAttribute ( 'openSSLTag' ))
2020-06-30 23:09:28 +12:00
);
}
2019-05-09 18:54:39 +12:00
2022-08-31 14:56:44 +12:00
switch ( $file -> getAttribute ( 'algorithm' , 'none' )) {
2022-08-31 01:46:55 +12:00
case 'zstd' :
if ( empty ( $source )) {
$source = $deviceFiles -> read ( $path );
}
$compressor = new Zstd ();
$source = $compressor -> decompress ( $source );
break ;
case 'gzip' :
if ( empty ( $source )) {
$source = $deviceFiles -> read ( $path );
}
$compressor = new GZIP ();
$source = $compressor -> decompress ( $source );
break ;
2021-07-15 22:35:08 +12:00
}
2019-05-09 18:54:39 +12:00
2021-12-09 23:44:44 +13:00
if ( ! empty ( $source )) {
if ( ! empty ( $rangeHeader )) {
2021-09-30 22:47:53 +13:00
$response -> send ( substr ( $source , $start , ( $end - $start + 1 )));
2021-09-13 18:49:35 +12:00
}
2021-07-15 22:35:08 +12:00
$response -> send ( $source );
}
2021-12-09 23:44:44 +13:00
if ( ! empty ( $rangeHeader )) {
2021-12-13 21:08:27 +13:00
$response -> send ( $deviceFiles -> read ( $path , $start , ( $end - $start + 1 )));
2021-09-13 18:49:35 +12:00
}
2021-12-13 21:08:27 +13:00
$size = $deviceFiles -> getFileSize ( $path );
2022-01-16 20:55:55 +13:00
if ( $size > APP_STORAGE_READ_BUFFER ) {
2021-12-13 21:08:27 +13:00
$response -> addHeader ( 'Content-Length' , $deviceFiles -> getFileSize ( $path ));
2022-01-16 20:55:55 +13:00
for ( $i = 0 ; $i < ceil ( $size / MAX_OUTPUT_CHUNK_SIZE ); $i ++ ) {
2021-12-09 23:44:44 +13:00
$response -> chunk (
2021-12-13 21:08:27 +13:00
$deviceFiles -> read (
2021-12-10 23:29:59 +13:00
$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 {
2021-12-13 21:08:27 +13:00
$response -> send ( $deviceFiles -> read ( $path ));
2021-07-15 22:35:08 +12:00
}
2020-12-27 05:48:36 +13:00
});
2019-05-09 18:54:39 +12:00
2021-06-20 23:20:35 +12:00
App :: put ( '/v1/storage/buckets/:bucketId/files/:fileId' )
2021-07-05 20:07:45 +12:00
-> alias ( '/v1/storage/files/:fileId' , [ 'bucketId' => 'default' ])
2019-08-29 00:29:45 +12:00
-> desc ( 'Update File' )
2020-06-26 06:32:12 +12:00
-> groups ([ 'api' , 'storage' ])
2019-08-29 00:29:45 +12:00
-> label ( 'scope' , 'files.write' )
2022-04-14 00:39:31 +12:00
-> label ( 'event' , 'buckets.[bucketId].files.[fileId].update' )
2022-09-05 20:00:08 +12:00
-> label ( 'audits.event' , 'file.update' )
2022-09-09 00:16:54 +12:00
-> label ( 'audits.resource' , 'file/{response.$id}' )
2022-08-11 14:18:22 +12:00
-> label ( 'usage.metric' , 'files.{scope}.requests.update' )
2022-08-21 14:03:19 +12:00
-> label ( 'usage.params' , [ 'bucketId:{request.bucketId}' ])
2022-08-31 15:58:32 +12:00
-> label ( 'abuse-key' , 'ip:{ip},method:{method},url:{url},userId:{userId}' )
2022-08-31 11:34:17 +12:00
-> label ( 'abuse-limit' , APP_LIMIT_WRITE_RATE_DEFAULT )
-> label ( 'abuse-time' , APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT )
2021-04-16 19:22:17 +12:00
-> label ( 'sdk.auth' , [ APP_AUTH_TYPE_SESSION , APP_AUTH_TYPE_KEY , APP_AUTH_TYPE_JWT ])
2019-08-29 00:29:45 +12:00
-> label ( 'sdk.namespace' , 'storage' )
2020-01-31 09:58:49 +13:00
-> label ( 'sdk.method' , 'updateFile' )
2019-10-08 20:09:35 +13:00
-> label ( 'sdk.description' , '/docs/references/storage/update-file.md' )
2020-11-12 10:02:24 +13:00
-> label ( 'sdk.response.code' , Response :: STATUS_CODE_OK )
-> label ( 'sdk.response.type' , Response :: CONTENT_TYPE_JSON )
-> label ( 'sdk.response.model' , Response :: MODEL_FILE )
2022-09-19 22:05:42 +12:00
-> param ( 'bucketId' , '' , new UID (), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).' )
2021-06-20 23:20:35 +12:00
-> param ( 'fileId' , '' , new UID (), 'File unique ID.' )
2022-09-06 21:17:25 +12:00
-> param ( 'permissions' , null , new Permissions ( APP_LIMIT_ARRAY_PARAMS_SIZE , [ Database :: PERMISSION_READ , Database :: PERMISSION_UPDATE , Database :: PERMISSION_DELETE , Database :: PERMISSION_WRITE ]), 'An array of permission string. By default the current permissions are inherited. [Learn more about permissions](/docs/permissions).' , true )
2020-12-27 05:48:36 +13:00
-> inject ( 'response' )
2021-12-28 01:45:23 +13:00
-> inject ( 'dbForProject' )
2021-12-11 05:48:54 +13:00
-> inject ( 'user' )
2021-11-19 22:45:42 +13:00
-> inject ( 'mode' )
2022-02-16 14:30:27 +13:00
-> inject ( 'events' )
2022-08-25 18:21:15 +12:00
-> action ( function ( string $bucketId , string $fileId , ? array $permissions , Response $response , Database $dbForProject , Document $user , string $mode , Event $events ) {
2022-08-08 22:58:36 +12:00
2022-03-15 22:51:51 +13:00
$bucket = Authorization :: skip ( fn () => $dbForProject -> getDocument ( 'buckets' , $bucketId ));
2022-08-02 21:19:15 +12:00
2022-08-08 22:58:36 +12:00
if ( $bucket -> isEmpty () || ( ! $bucket -> getAttribute ( 'enabled' ) && $mode !== APP_MODE_ADMIN )) {
2022-07-27 02:24:32 +12:00
throw new Exception ( Exception :: STORAGE_BUCKET_NOT_FOUND );
2022-08-02 21:19:15 +12:00
}
2019-08-29 00:29:45 +12:00
2022-08-08 22:58:36 +12:00
$fileSecurity = $bucket -> getAttributes ( 'fileSecurity' , false );
2022-08-25 01:24:54 +12:00
$validator = new Authorization ( Database :: PERMISSION_UPDATE );
2022-08-08 22:58:36 +12:00
$valid = $validator -> isValid ( $bucket -> getUpdate ());
2022-08-25 01:24:54 +12:00
if ( ! $fileSecurity && ! $valid ) {
2022-08-16 20:30:00 +12:00
throw new Exception ( Exception :: USER_UNAUTHORIZED );
2020-06-30 23:09:28 +12:00
}
2019-08-29 00:29:45 +12:00
2022-08-25 13:05:31 +12:00
// Read permission should not be required for update
$file = Authorization :: skip ( fn () => $dbForProject -> getDocument ( 'bucket_' . $bucket -> getInternalId (), $fileId ));
2019-08-29 00:29:45 +12:00
2022-08-26 15:01:16 +12:00
if ( $file -> isEmpty ()) {
2022-07-27 02:24:32 +12:00
throw new Exception ( Exception :: STORAGE_FILE_NOT_FOUND );
2020-06-30 23:09:28 +12:00
}
2020-01-05 04:45:28 +13:00
2022-08-25 15:51:21 +12:00
// Map aggregate permissions into the multiple permissions they represent.
$permissions = Permission :: aggregate ( $permissions , [
Database :: PERMISSION_READ ,
Database :: PERMISSION_UPDATE ,
Database :: PERMISSION_DELETE ,
]);
2022-08-16 00:56:19 +12:00
// Users can only manage their own roles, API keys and Admin users can manage any
$roles = Authorization :: getRoles ();
2022-08-26 15:01:16 +12:00
if ( ! Auth :: isAppUser ( $roles ) && ! Auth :: isPrivilegedUser ( $roles ) && ! \is_null ( $permissions )) {
2022-08-16 00:56:19 +12:00
foreach ( Database :: PERMISSIONS as $type ) {
foreach ( $permissions as $permission ) {
2022-08-16 23:26:38 +12:00
$permission = Permission :: parse ( $permission );
if ( $permission -> getPermission () != $type ) {
2022-08-16 00:56:19 +12:00
continue ;
}
2022-08-16 23:26:38 +12:00
$role = ( new Role (
$permission -> getRole (),
$permission -> getIdentifier (),
$permission -> getDimension ()
)) -> toString ();
2022-08-16 00:56:19 +12:00
if ( ! Authorization :: isRole ( $role )) {
2022-08-25 15:51:21 +12:00
throw new Exception ( Exception :: USER_UNAUTHORIZED , 'Permissions must be one of: (' . \implode ( ', ' , $roles ) . ')' );
2022-08-16 00:56:19 +12:00
}
}
}
2021-06-20 23:20:35 +12:00
}
2022-08-26 15:01:16 +12:00
if ( \is_null ( $permissions )) {
$permissions = $file -> getPermissions () ? ? [];
}
2022-08-02 21:19:15 +12:00
$file -> setAttribute ( '$permissions' , $permissions );
2022-03-15 22:51:51 +13:00
2022-08-25 23:30:26 +12:00
if ( $fileSecurity && ! $valid ) {
try {
$file = $dbForProject -> updateDocument ( 'bucket_' . $bucket -> getInternalId (), $fileId , $file );
} catch ( AuthorizationException ) {
throw new Exception ( Exception :: USER_UNAUTHORIZED );
}
} else {
$file = Authorization :: skip ( fn () => $dbForProject -> updateDocument ( 'bucket_' . $bucket -> getInternalId (), $fileId , $file ));
}
2021-06-20 23:20:35 +12:00
2022-04-14 00:39:31 +12:00
$events
-> setParam ( 'bucketId' , $bucket -> getId ())
-> setParam ( 'fileId' , $file -> getId ())
2022-06-22 22:51:49 +12:00
-> setContext ( 'bucket' , $bucket )
2020-06-30 23:09:28 +12:00
;
2020-10-31 08:53:27 +13:00
$response -> dynamic ( $file , Response :: MODEL_FILE );
2020-12-27 05:48:36 +13:00
});
2019-08-29 00:29:45 +12:00
2021-06-20 23:20:35 +12:00
App :: delete ( '/v1/storage/buckets/:bucketId/files/:fileId' )
2021-07-05 20:07:45 +12:00
-> alias ( '/v1/storage/files/:fileId' , [ 'bucketId' => 'default' ])
2019-05-09 18:54:39 +12:00
-> desc ( 'Delete File' )
2020-06-26 06:32:12 +12:00
-> groups ([ 'api' , 'storage' ])
2019-08-14 23:53:07 +12:00
-> label ( 'scope' , 'files.write' )
2022-04-14 00:39:31 +12:00
-> label ( 'event' , 'buckets.[bucketId].files.[fileId].delete' )
2022-09-05 20:00:08 +12:00
-> label ( 'audits.event' , 'file.delete' )
2022-08-15 16:29:44 +12:00
-> label ( 'audits.resource' , 'file/{request.fileId}' )
2022-08-11 14:18:22 +12:00
-> label ( 'usage.metric' , 'files.{scope}.requests.delete' )
2022-08-21 14:03:19 +12:00
-> label ( 'usage.params' , [ 'bucketId:{request.bucketId}' ])
2022-08-31 15:58:32 +12:00
-> label ( 'abuse-key' , 'ip:{ip},method:{method},url:{url},userId:{userId}' )
2022-08-31 11:34:17 +12:00
-> label ( 'abuse-limit' , APP_LIMIT_WRITE_RATE_DEFAULT )
-> label ( 'abuse-time' , APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT )
2021-04-16 19:22:17 +12:00
-> label ( 'sdk.auth' , [ APP_AUTH_TYPE_SESSION , APP_AUTH_TYPE_KEY , APP_AUTH_TYPE_JWT ])
2019-05-09 18:54:39 +12:00
-> label ( 'sdk.namespace' , 'storage' )
2020-01-31 09:58:49 +13:00
-> label ( 'sdk.method' , 'deleteFile' )
2019-10-08 20:09:35 +13:00
-> label ( 'sdk.description' , '/docs/references/storage/delete-file.md' )
2020-11-12 10:02:24 +13:00
-> label ( 'sdk.response.code' , Response :: STATUS_CODE_NOCONTENT )
-> label ( 'sdk.response.model' , Response :: MODEL_NONE )
2022-09-19 22:05:42 +12:00
-> param ( 'bucketId' , '' , new UID (), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).' )
2021-12-11 01:27:11 +13:00
-> param ( 'fileId' , '' , new UID (), 'File ID.' )
2020-12-27 05:48:36 +13:00
-> inject ( 'response' )
2021-12-28 01:45:23 +13:00
-> inject ( 'dbForProject' )
2020-12-27 05:48:36 +13:00
-> inject ( 'events' )
2021-11-19 22:45:42 +13:00
-> inject ( 'mode' )
2021-12-13 21:08:27 +13:00
-> inject ( 'deviceFiles' )
2022-08-15 21:05:41 +12:00
-> inject ( 'deletes' )
2022-08-18 12:49:31 +12:00
-> action ( function ( string $bucketId , string $fileId , Response $response , Database $dbForProject , Event $events , string $mode , Device $deviceFiles , Delete $deletes ) {
2022-03-15 22:51:51 +13:00
$bucket = Authorization :: skip ( fn () => $dbForProject -> getDocument ( 'buckets' , $bucketId ));
2019-05-09 18:54:39 +12:00
2022-08-08 22:58:36 +12:00
if ( $bucket -> isEmpty () || ( ! $bucket -> getAttribute ( 'enabled' ) && $mode !== APP_MODE_ADMIN )) {
2022-07-27 02:24:32 +12:00
throw new Exception ( Exception :: STORAGE_BUCKET_NOT_FOUND );
2020-06-30 23:09:28 +12:00
}
2019-05-09 18:54:39 +12:00
2022-08-08 22:58:36 +12:00
$fileSecurity = $bucket -> getAttributes ( 'fileSecurity' , false );
2022-08-25 13:05:31 +12:00
$validator = new Authorization ( Database :: PERMISSION_DELETE );
2022-08-08 22:58:36 +12:00
$valid = $validator -> isValid ( $bucket -> getDelete ());
2022-08-25 01:24:54 +12:00
if ( ! $fileSecurity && ! $valid ) {
2022-08-16 20:30:00 +12:00
throw new Exception ( Exception :: USER_UNAUTHORIZED );
2021-11-07 19:35:49 +13:00
}
2019-05-09 18:54:39 +12:00
2022-08-25 13:05:31 +12:00
// Read permission should not be required for delete
$file = Authorization :: skip ( fn () => $dbForProject -> getDocument ( 'bucket_' . $bucket -> getInternalId (), $fileId ));
2019-05-09 18:54:39 +12:00
2022-08-26 15:01:16 +12:00
if ( $file -> isEmpty ()) {
2022-07-27 02:24:32 +12:00
throw new Exception ( Exception :: STORAGE_FILE_NOT_FOUND );
2020-06-30 23:09:28 +12:00
}
2022-08-13 02:46:50 +12:00
2022-08-25 23:30:26 +12:00
// Make sure we don't delete the file before the document permission check occurs
if ( $fileSecurity && ! $valid && ! $validator -> isValid ( $file -> getDelete ())) {
throw new Exception ( Exception :: USER_UNAUTHORIZED );
}
2021-12-31 00:23:46 +13:00
$deviceDeleted = false ;
2022-01-16 20:55:55 +13:00
if ( $file -> getAttribute ( 'chunksTotal' ) !== $file -> getAttribute ( 'chunksUploaded' )) {
2022-01-16 20:47:03 +13:00
$deviceDeleted = $deviceFiles -> abort (
$file -> getAttribute ( 'path' ),
( $file -> getAttribute ( 'metadata' , [])[ 'uploadId' ] ? ? '' )
);
2021-12-31 00:23:46 +13:00
} else {
2022-01-07 23:02:38 +13:00
$deviceDeleted = $deviceFiles -> delete ( $file -> getAttribute ( 'path' ));
2021-12-31 00:23:46 +13:00
}
if ( $deviceDeleted ) {
2022-08-15 21:05:41 +12:00
$deletes
-> setType ( DELETE_TYPE_CACHE_BY_RESOURCE )
-> setResource ( 'file/' . $fileId )
;
2022-01-25 20:52:11 +13:00
2022-08-29 17:53:37 +12:00
if ( $fileSecurity && ! $valid ) {
try {
$deleted = $dbForProject -> deleteDocument ( 'bucket_' . $bucket -> getInternalId (), $fileId );
} catch ( AuthorizationException ) {
throw new Exception ( Exception :: USER_UNAUTHORIZED );
}
2022-08-25 13:05:31 +12:00
} else {
$deleted = Authorization :: skip ( fn () => $dbForProject -> deleteDocument ( 'bucket_' . $bucket -> getInternalId (), $fileId ));
}
2022-08-08 22:58:36 +12:00
2021-11-07 19:35:49 +13:00
if ( ! $deleted ) {
2022-07-27 02:24:32 +12:00
throw new Exception ( Exception :: GENERAL_SERVER_ERROR , 'Failed to remove file from DB' );
2019-05-09 18:54:39 +12:00
}
2021-07-08 23:26:11 +12:00
} else {
2022-07-27 02:24:32 +12:00
throw new Exception ( Exception :: GENERAL_SERVER_ERROR , 'Failed to delete file from device' );
2020-06-30 23:09:28 +12:00
}
2021-07-07 22:07:11 +12:00
2020-12-07 11:14:57 +13:00
$events
2022-04-14 00:39:31 +12:00
-> setParam ( 'bucketId' , $bucket -> getId ())
-> setParam ( 'fileId' , $file -> getId ())
2022-06-22 22:51:49 +12:00
-> setContext ( 'bucket' , $bucket )
2022-04-14 00:39:31 +12:00
-> setPayload ( $response -> output ( $file , Response :: MODEL_FILE ))
2020-10-31 08:53:27 +13:00
;
2020-06-30 23:09:28 +12:00
$response -> noContent ();
2020-12-27 05:48:36 +13:00
});
2019-05-09 18:54:39 +12:00
2021-08-21 00:17:19 +12:00
App :: get ( '/v1/storage/usage' )
-> desc ( 'Get usage stats for storage' )
-> groups ([ 'api' , 'storage' ])
2021-08-27 06:15:36 +12:00
-> label ( 'scope' , 'files.read' )
2021-08-21 00:17:19 +12:00
-> label ( 'sdk.auth' , [ APP_AUTH_TYPE_ADMIN ])
-> label ( 'sdk.namespace' , 'storage' )
-> label ( 'sdk.method' , 'getUsage' )
2021-08-27 06:44:32 +12:00
-> label ( 'sdk.response.code' , Response :: STATUS_CODE_OK )
-> label ( 'sdk.response.type' , Response :: CONTENT_TYPE_JSON )
2021-08-28 05:34:43 +12:00
-> label ( 'sdk.response.model' , Response :: MODEL_USAGE_STORAGE )
2021-08-21 00:17:19 +12:00
-> param ( 'range' , '30d' , new WhiteList ([ '24h' , '7d' , '30d' , '90d' ], true ), 'Date range.' , true )
-> inject ( 'response' )
2021-12-28 01:45:23 +13:00
-> inject ( 'dbForProject' )
2022-05-05 00:23:34 +12:00
-> action ( function ( string $range , Response $response , Database $dbForProject ) {
2021-08-21 00:17:19 +12:00
2021-08-27 06:44:32 +12:00
$usage = [];
2021-11-07 18:50:21 +13:00
if ( App :: getEnv ( '_APP_USAGE_STATS' , 'enabled' ) === 'enabled' ) {
2021-10-28 11:17:15 +13:00
$periods = [
2021-08-21 00:17:19 +12:00
'24h' => [
2022-10-21 18:45:25 +13:00
'period' => '1h' ,
'limit' => 24 ,
2021-08-21 00:17:19 +12:00
],
'7d' => [
'period' => '1d' ,
'limit' => 7 ,
],
'30d' => [
'period' => '1d' ,
'limit' => 30 ,
],
'90d' => [
'period' => '1d' ,
'limit' => 90 ,
],
];
2021-08-27 06:44:32 +12:00
$metrics = [
2022-08-11 14:18:22 +12:00
'project.$all.storage.size' ,
'buckets.$all.count.total' ,
'buckets.$all.requests.create' ,
'buckets.$all.requests.read' ,
'buckets.$all.requests.update' ,
'buckets.$all.requests.delete' ,
2022-08-13 17:01:43 +12:00
'files.$all.storage.size' ,
'files.$all.count.total' ,
2022-08-11 14:18:22 +12:00
'files.$all.requests.create' ,
'files.$all.requests.read' ,
'files.$all.requests.update' ,
'files.$all.requests.delete' ,
2021-08-27 06:44:32 +12:00
];
2021-08-21 00:17:19 +12:00
2021-08-27 06:44:32 +12:00
$stats = [];
2021-08-29 04:25:48 +12:00
2022-01-16 20:55:55 +13:00
Authorization :: skip ( function () use ( $dbForProject , $periods , $range , $metrics , & $stats ) {
2021-08-29 04:25:48 +12:00
foreach ( $metrics as $metric ) {
2021-10-28 11:17:15 +13:00
$limit = $periods [ $range ][ 'limit' ];
$period = $periods [ $range ][ 'period' ];
2021-10-28 08:57:20 +13:00
2021-12-28 01:45:23 +13:00
$requestDocs = $dbForProject -> find ( 'stats' , [
2022-08-12 11:53:52 +12:00
Query :: equal ( 'period' , [ $period ]),
Query :: equal ( 'metric' , [ $metric ]),
Query :: limit ( $limit ),
Query :: orderDesc ( 'time' ),
]);
2021-10-27 02:19:28 +13:00
2021-08-29 04:25:48 +12:00
$stats [ $metric ] = [];
foreach ( $requestDocs as $requestDoc ) {
$stats [ $metric ][] = [
'value' => $requestDoc -> getAttribute ( 'value' ),
'date' => $requestDoc -> getAttribute ( 'time' ),
];
}
2021-10-28 08:57:20 +13:00
// backfill metrics with empty values for graphs
2022-05-24 02:54:50 +12:00
$backfill = $limit - \count ( $requestDocs );
2021-10-28 08:57:20 +13:00
while ( $backfill > 0 ) {
$last = $limit - $backfill - 1 ; // array index of last added metric
2022-05-24 02:54:50 +12:00
$diff = match ( $period ) { // convert period to seconds for unix timestamp math
2022-10-21 18:45:25 +13:00
'1h' => 3600 ,
2021-10-28 08:57:20 +13:00
'1d' => 86400 ,
};
$stats [ $metric ][] = [
'value' => 0 ,
2022-09-16 11:48:09 +12:00
'date' => DateTime :: formatTz ( DateTime :: addSeconds ( new \DateTime ( $stats [ $metric ][ $last ][ 'date' ] ? ? null ), - 1 * $diff )),
2021-10-28 08:57:20 +13:00
];
$backfill -- ;
}
2021-08-29 04:25:48 +12:00
$stats [ $metric ] = array_reverse ( $stats [ $metric ]);
2021-10-27 02:19:28 +13:00
}
2021-08-29 04:25:48 +12:00
});
2021-08-21 00:17:19 +12:00
2021-08-27 06:44:32 +12:00
$usage = new Document ([
2021-08-21 00:17:19 +12:00
'range' => $range ,
2022-08-11 14:18:22 +12:00
'bucketsCount' => $stats [ 'buckets.$all.count.total' ],
'bucketsCreate' => $stats [ 'buckets.$all.requests.create' ],
'bucketsRead' => $stats [ 'buckets.$all.requests.read' ],
'bucketsUpdate' => $stats [ 'buckets.$all.requests.update' ],
'bucketsDelete' => $stats [ 'buckets.$all.requests.delete' ],
2022-08-13 17:01:43 +12:00
'storage' => $stats [ 'project.$all.storage.size' ],
'filesCount' => $stats [ 'files.$all.count.total' ],
2022-08-11 14:18:22 +12:00
'filesCreate' => $stats [ 'files.$all.requests.create' ],
'filesRead' => $stats [ 'files.$all.requests.read' ],
'filesUpdate' => $stats [ 'files.$all.requests.update' ],
'filesDelete' => $stats [ 'files.$all.requests.delete' ],
2021-08-21 00:17:19 +12:00
]);
}
2021-08-27 06:44:32 +12:00
2021-08-28 05:34:43 +12:00
$response -> dynamic ( $usage , Response :: MODEL_USAGE_STORAGE );
2021-08-21 00:17:19 +12:00
});
2021-08-20 20:58:42 +12:00
App :: get ( '/v1/storage/:bucketId/usage' )
2021-08-21 00:10:52 +12:00
-> desc ( 'Get usage stats for a storage bucket' )
2021-08-20 20:58:42 +12:00
-> groups ([ 'api' , 'storage' ])
2021-08-27 06:15:36 +12:00
-> label ( 'scope' , 'files.read' )
2021-08-20 20:58:42 +12:00
-> label ( 'sdk.auth' , [ APP_AUTH_TYPE_ADMIN ])
-> label ( 'sdk.namespace' , 'storage' )
2021-09-01 08:55:13 +12:00
-> label ( 'sdk.method' , 'getBucketUsage' )
2021-08-27 06:44:32 +12:00
-> label ( 'sdk.response.code' , Response :: STATUS_CODE_OK )
-> label ( 'sdk.response.type' , Response :: CONTENT_TYPE_JSON )
2021-08-28 05:34:43 +12:00
-> label ( 'sdk.response.model' , Response :: MODEL_USAGE_BUCKETS )
2021-12-11 01:27:11 +13:00
-> param ( 'bucketId' , '' , new UID (), 'Bucket ID.' )
2021-08-20 20:58:42 +12:00
-> param ( 'range' , '30d' , new WhiteList ([ '24h' , '7d' , '30d' , '90d' ], true ), 'Date range.' , true )
-> inject ( 'response' )
2021-12-28 01:45:23 +13:00
-> inject ( 'dbForProject' )
2022-05-05 00:23:34 +12:00
-> action ( function ( string $bucketId , string $range , Response $response , Database $dbForProject ) {
2021-08-20 20:58:42 +12:00
2022-01-06 20:26:23 +13:00
$bucket = $dbForProject -> getDocument ( 'buckets' , $bucketId );
2021-09-12 18:14:40 +12:00
2021-12-09 23:44:44 +13:00
if ( $bucket -> isEmpty ()) {
2022-07-27 02:24:32 +12:00
throw new Exception ( Exception :: STORAGE_BUCKET_NOT_FOUND );
2022-01-16 20:55:55 +13:00
}
2021-08-27 06:44:32 +12:00
$usage = [];
2021-11-07 18:50:21 +13:00
if ( App :: getEnv ( '_APP_USAGE_STATS' , 'enabled' ) === 'enabled' ) {
2021-10-28 11:17:15 +13:00
$periods = [
2021-08-20 20:58:42 +12:00
'24h' => [
2022-10-21 14:40:33 +13:00
'period' => '1h' ,
2022-10-21 18:45:25 +13:00
'limit' => 24 ,
2021-08-20 20:58:42 +12:00
],
'7d' => [
'period' => '1d' ,
'limit' => 7 ,
],
'30d' => [
'period' => '1d' ,
'limit' => 30 ,
],
'90d' => [
'period' => '1d' ,
'limit' => 90 ,
],
];
$metrics = [
2022-08-13 16:39:01 +12:00
" files. { $bucketId } .count.total " ,
" files. { $bucketId } .storage.size " ,
" files. { $bucketId } .requests.create " ,
" files. { $bucketId } .requests.read " ,
" files. { $bucketId } .requests.update " ,
" files. { $bucketId } .requests.delete " ,
2021-08-20 20:58:42 +12:00
];
2021-08-27 06:44:32 +12:00
$stats = [];
2021-08-29 04:25:48 +12:00
2022-01-16 20:55:55 +13:00
Authorization :: skip ( function () use ( $dbForProject , $periods , $range , $metrics , & $stats ) {
2021-08-29 04:25:48 +12:00
foreach ( $metrics as $metric ) {
2021-10-28 11:17:15 +13:00
$limit = $periods [ $range ][ 'limit' ];
$period = $periods [ $range ][ 'period' ];
2022-09-03 02:19:36 +12:00
2021-12-28 01:45:23 +13:00
$requestDocs = $dbForProject -> find ( 'stats' , [
2022-08-12 11:53:52 +12:00
Query :: equal ( 'period' , [ $period ]),
Query :: equal ( 'metric' , [ $metric ]),
Query :: limit ( $limit ),
Query :: orderDesc ( 'time' ),
]);
2021-10-27 02:19:28 +13:00
2021-08-29 04:25:48 +12:00
$stats [ $metric ] = [];
foreach ( $requestDocs as $requestDoc ) {
$stats [ $metric ][] = [
'value' => $requestDoc -> getAttribute ( 'value' ),
'date' => $requestDoc -> getAttribute ( 'time' ),
];
}
2021-10-28 08:57:20 +13:00
// backfill metrics with empty values for graphs
2022-05-24 02:54:50 +12:00
$backfill = $limit - \count ( $requestDocs );
2021-10-28 08:57:20 +13:00
while ( $backfill > 0 ) {
$last = $limit - $backfill - 1 ; // array index of last added metric
2022-05-24 02:54:50 +12:00
$diff = match ( $period ) { // convert period to seconds for unix timestamp math
2022-10-21 18:45:25 +13:00
'1h' => 3600 ,
2021-10-28 08:57:20 +13:00
'1d' => 86400 ,
};
$stats [ $metric ][] = [
'value' => 0 ,
2022-09-16 11:48:09 +12:00
'date' => DateTime :: formatTz ( DateTime :: addSeconds ( new \DateTime ( $stats [ $metric ][ $last ][ 'date' ] ? ? null ), - 1 * $diff )),
2021-10-28 08:57:20 +13:00
];
$backfill -- ;
}
2021-08-29 04:25:48 +12:00
$stats [ $metric ] = array_reverse ( $stats [ $metric ]);
2021-10-27 02:19:28 +13:00
}
2021-08-29 04:25:48 +12:00
});
2021-08-20 21:00:39 +12:00
2021-08-27 06:44:32 +12:00
$usage = new Document ([
2021-08-20 21:00:39 +12:00
'range' => $range ,
2022-08-13 16:39:01 +12:00
'filesCount' => $stats [ $metrics [ 0 ]],
'filesStorage' => $stats [ $metrics [ 1 ]],
'filesCreate' => $stats [ $metrics [ 2 ]],
'filesRead' => $stats [ $metrics [ 3 ]],
'filesUpdate' => $stats [ $metrics [ 4 ]],
'filesDelete' => $stats [ $metrics [ 5 ]],
2021-08-20 21:00:39 +12:00
]);
}
2021-08-27 06:44:32 +12:00
2021-08-28 05:34:43 +12:00
$response -> dynamic ( $usage , Response :: MODEL_USAGE_BUCKETS );
2021-12-21 00:09:50 +13:00
});