diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index e8efb64375..eec8a6b766 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -247,6 +247,7 @@ App::delete('/v1/storage/buckets/:bucketId') }); App::post('/v1/storage/buckets/:bucketId/files') + ->alias('/v1/storage/files',['bucketId' => 'default']) ->desc('Create File') ->groups(['api', 'storage']) ->label('scope', 'files.write') @@ -402,6 +403,7 @@ App::post('/v1/storage/buckets/:bucketId/files') }); App::get('/v1/storage/buckets/:bucketId/files') + ->alias('/v1/storage/files', ['bucketId' => 'default']) ->desc('List Files') ->groups(['api', 'storage']) ->label('scope', 'files.read') @@ -442,6 +444,7 @@ App::get('/v1/storage/buckets/:bucketId/files') }); App::get('/v1/storage/buckets/:bucketId/files/:fileId') + ->alias('/v1/storage/files/:fileId', ['bucketId' => 'default']) ->desc('Get File') ->groups(['api', 'storage']) ->label('scope', 'files.read') @@ -476,6 +479,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId') }); App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview') + ->alias('/v1/storage/files/:fileId/preview', ['bucketId' => 'default']) ->desc('Get File Preview') ->groups(['api', 'storage']) ->label('scope', 'files.read') @@ -635,6 +639,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview') }); App::get('/v1/storage/buckets/:bucketId/files/:fileId/download') + ->alias('/v1/storage/files/:fileId/download', ['bucketId' => 'default']) ->desc('Get File for Download') ->groups(['api', 'storage']) ->label('scope', 'files.read') @@ -700,6 +705,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download') }); App::get('/v1/storage/buckets/:bucketId/files/:fileId/view') + ->alias('/v1/storage/files/:fileId/view', ['bucketId' => 'default']) ->desc('Get File for View') ->groups(['api', 'storage']) ->label('scope', 'files.read') @@ -775,6 +781,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view') }); App::put('/v1/storage/buckets/:bucketId/files/:fileId') + ->alias('/v1/storage/files/:fileId', ['bucketId' => 'default']) ->desc('Update File') ->groups(['api', 'storage']) ->label('scope', 'files.write') @@ -824,6 +831,7 @@ App::put('/v1/storage/buckets/:bucketId/files/:fileId') }); App::delete('/v1/storage/buckets/:bucketId/files/:fileId') + ->alias('/v1/storage/files/:fileId', ['bucketId' => 'default']) ->desc('Delete File') ->groups(['api', 'storage']) ->label('scope', 'files.write') @@ -883,567 +891,3 @@ App::delete('/v1/storage/buckets/:bucketId/files/:fileId') $response->noContent(); }); - -App::post('/v1/storage/files') - ->desc('Create File') - ->groups(['api', 'storage']) - ->label('scope', 'files.write') - ->label('event', 'storage.files.create') - ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT]) - ->label('sdk.namespace', 'storage') - ->label('sdk.method', 'createFile') - ->label('sdk.description', '/docs/references/storage/create-file.md') - ->label('sdk.request.type', 'multipart/form-data') - ->label('sdk.methodType', 'upload') - ->label('sdk.response.code', Response::STATUS_CODE_CREATED) - ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) - ->label('sdk.response.model', Response::MODEL_FILE) - ->param('file', [], new File(), 'Binary file.', false) - ->param('read', null, new ArrayList(new Text(64)), 'An array of strings with read permissions. By default only the current user is granted with read permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.', true) - ->param('write', null, new ArrayList(new Text(64)), 'An array of strings with write permissions. By default only the current user is granted with write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.', true) - ->inject('request') - ->inject('response') - ->inject('dbForInternal') - ->inject('user') - ->inject('audits') - ->inject('usage') - ->action(function ($file, $read, $write, $request, $response, $dbForInternal, $user, $audits, $usage) { - /** @var Utopia\Swoole\Request $request */ - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Database\Database $dbForInternal */ - /** @var Appwrite\Database\Document $user */ - /** @var Appwrite\Event\Event $audits */ - /** @var Appwrite\Event\Event $usage */ - - $file = $request->getFiles('file'); - - /* - * Validators - */ - //$fileType = new FileType(array(FileType::FILE_TYPE_PNG, FileType::FILE_TYPE_GIF, FileType::FILE_TYPE_JPEG)); - $fileSize = new FileSize(App::getEnv('_APP_STORAGE_LIMIT', 0)); - $upload = new Upload(); - - if (empty($file)) { - throw new Exception('No file sent', 400); - } - - // Make sure we handle a single file and multiple files the same way - $file['name'] = (\is_array($file['name']) && isset($file['name'][0])) ? $file['name'][0] : $file['name']; - $file['tmp_name'] = (\is_array($file['tmp_name']) && isset($file['tmp_name'][0])) ? $file['tmp_name'][0] : $file['tmp_name']; - $file['size'] = (\is_array($file['size']) && isset($file['size'][0])) ? $file['size'][0] : $file['size']; - - // Check if file type is allowed (feature for project settings?) - //if (!$fileType->isValid($file['tmp_name'])) { - //throw new Exception('File type not allowed', 400); - //} - - if (!$fileSize->isValid($file['size'])) { // Check if file size is exceeding allowed limit - throw new Exception('File size not allowed', 400); - } - - $device = Storage::getDevice('files'); - - if (!$upload->isValid($file['tmp_name'])) { - throw new Exception('Invalid file', 403); - } - - // Save to storage - $size = $device->getFileSize($file['tmp_name']); - $path = $device->getPath(\uniqid().'.'.\pathinfo($file['name'], PATHINFO_EXTENSION)); - - if (!$device->upload($file['tmp_name'], $path)) { // TODO deprecate 'upload' and replace with 'move' - throw new Exception('Failed moving file', 500); - } - - $mimeType = $device->getFileMimeType($path); // Get mime-type before compression and encryption - - if (App::getEnv('_APP_STORAGE_ANTIVIRUS') === 'enabled') { // Check if scans are enabled - $antiVirus = new Network(App::getEnv('_APP_STORAGE_ANTIVIRUS_HOST', 'clamav'), - (int) App::getEnv('_APP_STORAGE_ANTIVIRUS_PORT', 3310)); - - if (!$antiVirus->fileScan($path)) { - $device->delete($path); - throw new Exception('Invalid file', 403); - } - } - - // Compression - $compressor = new GZIP(); - $data = $device->read($path); - $data = $compressor->compress($data); - $key = App::getEnv('_APP_OPENSSL_KEY_V1'); - $iv = OpenSSL::randomPseudoBytes(OpenSSL::cipherIVLength(OpenSSL::CIPHER_AES_128_GCM)); - $data = OpenSSL::encrypt($data, OpenSSL::CIPHER_AES_128_GCM, $key, 0, $iv, $tag); - - if (!$device->write($path, $data, $mimeType)) { - throw new Exception('Failed to save file', 500); - } - - $sizeActual = $device->getFileSize($path); - - $file = $dbForInternal->createDocument('files', new Document([ - '$read' => (is_null($read) && !$user->isEmpty()) ? ['user:'.$user->getId()] : $read ?? [], // By default set read permissions for user - '$write' => (is_null($write) && !$user->isEmpty()) ? ['user:'.$user->getId()] : $write ?? [], // By default set write permissions for user - 'dateCreated' => \time(), - 'bucketId' => '', - 'name' => $file['name'], - 'path' => $path, - 'signature' => $device->getFileHash($path), - 'mimeType' => $mimeType, - 'sizeOriginal' => $size, - 'sizeActual' => $sizeActual, - 'algorithm' => $compressor->getName(), - 'comment' => '', - 'openSSLVersion' => '1', - 'openSSLCipher' => OpenSSL::CIPHER_AES_128_GCM, - 'openSSLTag' => \bin2hex($tag), - 'openSSLIV' => \bin2hex($iv), - ])); - - $audits - ->setParam('event', 'storage.files.create') - ->setParam('resource', 'storage/files/'.$file->getId()) - ; - - $usage - ->setParam('storage', $sizeActual) - ; - - $response->setStatusCode(Response::STATUS_CODE_CREATED); - $response->dynamic2($file, Response::MODEL_FILE); - ; - }); - -App::get('/v1/storage/files') - ->desc('List Files') - ->groups(['api', 'storage']) - ->label('scope', 'files.read') - ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT]) - ->label('sdk.namespace', 'storage') - ->label('sdk.method', 'listFiles') - ->label('sdk.description', '/docs/references/storage/list-files.md') - ->label('sdk.response.code', Response::STATUS_CODE_OK) - ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) - ->label('sdk.response.model', Response::MODEL_FILE_LIST) - ->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true) - ->param('limit', 25, new Range(0, 100), 'Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true) - ->param('offset', 0, new Range(0, 2000), 'Results offset. The default value is 0. Use this param to manage pagination.', true) - ->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true) - ->inject('response') - ->inject('dbForInternal') - ->action(function ($search, $limit, $offset, $orderType, $response, $dbForInternal) { - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Database\Database $dbForInternal */ - - $queries = ($search) ? [new Query('name', Query::TYPE_SEARCH, $search)] : []; - - $response->dynamic2(new Document([ - 'files' => $dbForInternal->find('files', $queries, $limit, $offset, ['_id'], [$orderType]), - 'sum' => $dbForInternal->count('files', $queries, APP_LIMIT_COUNT), - ]), Response::MODEL_FILE_LIST); - }); - -App::get('/v1/storage/files/:fileId') - ->desc('Get File') - ->groups(['api', 'storage']) - ->label('scope', 'files.read') - ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT]) - ->label('sdk.namespace', 'storage') - ->label('sdk.method', 'getFile') - ->label('sdk.description', '/docs/references/storage/get-file.md') - ->label('sdk.response.code', Response::STATUS_CODE_OK) - ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) - ->label('sdk.response.model', Response::MODEL_FILE) - ->param('fileId', '', new UID(), 'File unique ID.') - ->inject('response') - ->inject('dbForInternal') - ->action(function ($fileId, $response, $dbForInternal) { - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Database\Database $dbForInternal */ - - $file = $dbForInternal->getDocument('files', $fileId); - - if (empty($file->getId())) { - throw new Exception('File not found', 404); - } - - $response->dynamic2($file, Response::MODEL_FILE); - }); - -App::get('/v1/storage/files/:fileId/preview') - ->desc('Get File Preview') - ->groups(['api', 'storage']) - ->label('scope', 'files.read') - ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT]) - ->label('sdk.namespace', 'storage') - ->label('sdk.method', 'getFilePreview') - ->label('sdk.description', '/docs/references/storage/get-file-preview.md') - ->label('sdk.response.code', Response::STATUS_CODE_OK) - ->label('sdk.response.type', Response::CONTENT_TYPE_IMAGE) - ->label('sdk.methodType', 'location') - ->param('fileId', '', new UID(), 'File unique ID') - ->param('width', 0, new Range(0, 4000), 'Resize preview image width, Pass an integer between 0 to 4000.', true) - ->param('height', 0, new Range(0, 4000), 'Resize preview image height, Pass an integer between 0 to 4000.', true) - ->param('gravity', Image::GRAVITY_CENTER, new WhiteList(Image::getGravityTypes()), 'Image crop gravity. Can be one of ' . implode(",", Image::getGravityTypes()), true) - ->param('quality', 100, new Range(0, 100), 'Preview image quality. Pass an integer between 0 to 100. Defaults to 100.', true) - ->param('borderWidth', 0, new Range(0, 100), 'Preview image border in pixels. Pass an integer between 0 to 100. Defaults to 0.', true) - ->param('borderColor', '', new HexColor(), 'Preview image border color. Use a valid HEX color, no # is needed for prefix.', true) - ->param('borderRadius', 0, new Range(0, 4000), 'Preview image border radius in pixels. Pass an integer between 0 to 4000.', true) - ->param('opacity', 1, new Range(0,1, Range::TYPE_FLOAT), 'Preview image opacity. Only works with images having an alpha channel (like png). Pass a number between 0 to 1.', true) - ->param('rotation', 0, new Range(0,360), 'Preview image rotation in degrees. Pass an integer between 0 and 360.', true) - ->param('background', '', new HexColor(), 'Preview image background color. Only works with transparent images (png). Use a valid HEX color, no # is needed for prefix.', true) - ->param('output', '', new WhiteList(\array_keys(Config::getParam('storage-outputs')), true), 'Output format type (jpeg, jpg, png, gif and webp).', true) - ->inject('request') - ->inject('response') - ->inject('project') - ->inject('dbForInternal') - ->action(function ($fileId, $width, $height, $gravity, $quality, $borderWidth, $borderColor, $borderRadius, $opacity, $rotation, $background, $output, $request, $response, $project, $dbForInternal) { - /** @var Utopia\Swoole\Request $request */ - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Database\Document $project */ - /** @var Utopia\Database\Database $dbForInternal */ - - $storage = 'files'; - - if (!\extension_loaded('imagick')) { - throw new Exception('Imagick extension is missing', 500); - } - - if (!Storage::exists($storage)) { - throw new Exception('No such storage device', 400); - } - - if ((\strpos($request->getAccept(), 'image/webp') === false) && ('webp' == $output)) { // Fallback webp to jpeg when no browser support - $output = 'jpg'; - } - - $inputs = Config::getParam('storage-inputs'); - $outputs = Config::getParam('storage-outputs'); - $fileLogos = Config::getParam('storage-logos'); - - $date = \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)).' GMT'; // 45 days cache - $key = \md5($fileId.$width.$height.$gravity.$quality.$borderWidth.$borderColor.$borderRadius.$opacity.$rotation.$background.$storage.$output); - - $file = $dbForInternal->getDocument('files', $fileId); - - if (empty($file->getId())) { - throw new Exception('File not found', 404); - } - - $path = $file->getAttribute('path'); - $type = \strtolower(\pathinfo($path, PATHINFO_EXTENSION)); - $algorithm = $file->getAttribute('algorithm'); - $cipher = $file->getAttribute('openSSLCipher'); - $mime = $file->getAttribute('mimeType'); - - if (!\in_array($mime, $inputs)) { - $path = (\array_key_exists($mime, $fileLogos)) ? $fileLogos[$mime] : $fileLogos['default']; - $algorithm = null; - $cipher = null; - $background = (empty($background)) ? 'eceff1' : $background; - $type = \strtolower(\pathinfo($path, PATHINFO_EXTENSION)); - $key = \md5($path.$width.$height.$gravity.$quality.$borderWidth.$borderColor.$borderRadius.$opacity.$rotation.$background.$storage.$output); - } - - $compressor = new GZIP(); - $device = Storage::getDevice('files'); - - if (!\file_exists($path)) { - throw new Exception('File not found', 404); - } - - $cache = new Cache(new Filesystem(APP_STORAGE_CACHE.'/app-'.$project->getId())); // Limit file number or size - $data = $cache->load($key, 60 * 60 * 24 * 30 * 3 /* 3 months */); - - if ($data) { - $output = (empty($output)) ? $type : $output; - - return $response - ->setContentType((\array_key_exists($output, $outputs)) ? $outputs[$output] : $outputs['jpg']) - ->addHeader('Expires', $date) - ->addHeader('X-Appwrite-Cache', 'hit') - ->send($data) - ; - } - - $source = $device->read($path); - - if (!empty($cipher)) { // Decrypt - $source = OpenSSL::decrypt( - $source, - $file->getAttribute('openSSLCipher'), - App::getEnv('_APP_OPENSSL_KEY_V'.$file->getAttribute('openSSLVersion')), - 0, - \hex2bin($file->getAttribute('openSSLIV')), - \hex2bin($file->getAttribute('openSSLTag')) - ); - } - - if (!empty($algorithm)) { - $source = $compressor->decompress($source); - } - - $image = new Image($source); - - $image->crop((int) $width, (int) $height, $gravity); - - if (!empty($opacity) || $opacity==0) { - $image->setOpacity($opacity); - } - - if (!empty($background)) { - $image->setBackground('#'.$background); - } - - - if (!empty($borderWidth) ) { - $image->setBorder($borderWidth, '#'.$borderColor); - } - - if (!empty($borderRadius)) { - $image->setBorderRadius($borderRadius); - } - - if (!empty($rotation)) { - $image->setRotation($rotation); - } - - $output = (empty($output)) ? $type : $output; - - $data = $image->output($output, $quality); - - $cache->save($key, $data); - - $response - ->setContentType($outputs[$output]) - ->addHeader('Expires', $date) - ->addHeader('X-Appwrite-Cache', 'miss') - ->send($data) - ; - - unset($image); - }); - -App::get('/v1/storage/files/:fileId/download') - ->desc('Get File for Download') - ->groups(['api', 'storage']) - ->label('scope', 'files.read') - ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT]) - ->label('sdk.namespace', 'storage') - ->label('sdk.method', 'getFileDownload') - ->label('sdk.description', '/docs/references/storage/get-file-download.md') - ->label('sdk.response.code', Response::STATUS_CODE_OK) - ->label('sdk.response.type', '*/*') - ->label('sdk.methodType', 'location') - ->param('fileId', '', new UID(), 'File unique ID.') - ->inject('response') - ->inject('dbForInternal') - ->action(function ($fileId, $response, $dbForInternal) { - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Database\Database $dbForInternal */ - - $file = $dbForInternal->getDocument('files', $fileId); - - if (empty($file->getId())) { - throw new Exception('File not found', 404); - } - - $path = $file->getAttribute('path', ''); - - if (!\file_exists($path)) { - throw new Exception('File not found in '.$path, 404); - } - - $compressor = new GZIP(); - $device = Storage::getDevice('files'); - - $source = $device->read($path); - - if (!empty($file->getAttribute('openSSLCipher'))) { // Decrypt - $source = OpenSSL::decrypt( - $source, - $file->getAttribute('openSSLCipher'), - App::getEnv('_APP_OPENSSL_KEY_V'.$file->getAttribute('openSSLVersion')), - 0, - \hex2bin($file->getAttribute('openSSLIV')), - \hex2bin($file->getAttribute('openSSLTag')) - ); - } - - $source = $compressor->decompress($source); - - // Response - $response - ->setContentType($file->getAttribute('mimeType')) - ->addHeader('Content-Disposition', 'attachment; 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()) - ->send($source) - ; - }); - -App::get('/v1/storage/files/:fileId/view') - ->desc('Get File for View') - ->groups(['api', 'storage']) - ->label('scope', 'files.read') - ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT]) - ->label('sdk.namespace', 'storage') - ->label('sdk.method', 'getFileView') - ->label('sdk.description', '/docs/references/storage/get-file-view.md') - ->label('sdk.response.code', Response::STATUS_CODE_OK) - ->label('sdk.response.type', '*/*') - ->label('sdk.methodType', 'location') - ->param('fileId', '', new UID(), 'File unique ID.') - ->inject('response') - ->inject('dbForInternal') - ->action(function ($fileId, $response, $dbForInternal) { - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Database\Database $dbForInternal */ - - $file = $dbForInternal->getDocument('files', $fileId); - $mimes = Config::getParam('storage-mimes'); - - if (empty($file->getId())) { - throw new Exception('File not found', 404); - } - - $path = $file->getAttribute('path', ''); - - if (!\file_exists($path)) { - throw new Exception('File not found in '.$path, 404); - } - - $compressor = new GZIP(); - $device = Storage::getDevice('files'); - - $contentType = 'text/plain'; - - if (\in_array($file->getAttribute('mimeType'), $mimes)) { - $contentType = $file->getAttribute('mimeType'); - } - - $source = $device->read($path); - - if (!empty($file->getAttribute('openSSLCipher'))) { // Decrypt - $source = OpenSSL::decrypt( - $source, - $file->getAttribute('openSSLCipher'), - App::getEnv('_APP_OPENSSL_KEY_V'.$file->getAttribute('openSSLVersion')), - 0, - \hex2bin($file->getAttribute('openSSLIV')), - \hex2bin($file->getAttribute('openSSLTag')) - ); - } - - $output = $compressor->decompress($source); - $fileName = $file->getAttribute('name', ''); - - // Response - $response - ->setContentType($contentType) - ->addHeader('Content-Security-Policy', 'script-src none;') - ->addHeader('X-Content-Type-Options', 'nosniff') - ->addHeader('Content-Disposition', 'inline; filename="'.$fileName.'"') - ->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()) - ->send($output) - ; - }); - -App::put('/v1/storage/files/:fileId') - ->desc('Update File') - ->groups(['api', 'storage']) - ->label('scope', 'files.write') - ->label('event', 'storage.files.update') - ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT]) - ->label('sdk.namespace', 'storage') - ->label('sdk.method', 'updateFile') - ->label('sdk.description', '/docs/references/storage/update-file.md') - ->label('sdk.response.code', Response::STATUS_CODE_OK) - ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) - ->label('sdk.response.model', Response::MODEL_FILE) - ->param('fileId', '', new UID(), 'File unique ID.') - ->param('read', [], new ArrayList(new Text(64)), 'An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.') - ->param('write', [], new ArrayList(new Text(64)), 'An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.') - ->inject('response') - ->inject('dbForInternal') - ->inject('audits') - ->action(function ($fileId, $read, $write, $response, $dbForInternal, $audits) { - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Database\Database $dbForInternal */ - /** @var Appwrite\Event\Event $audits */ - - $file = $dbForInternal->getDocument('files', $fileId); - - if (empty($file->getId())) { - throw new Exception('File not found', 404); - } - - $file = $dbForInternal->updateDocument('files', $fileId, new Document(\array_merge($file->getArrayCopy(), [ - '$read' => $read, - '$write' => $write, - 'bucketId' => '', - ]))); - - $audits - ->setParam('event', 'storage.files.update') - ->setParam('resource', 'storage/files/'.$file->getId()) - ; - - $response->dynamic2($file, Response::MODEL_FILE); - }); - -App::delete('/v1/storage/files/:fileId') - ->desc('Delete File') - ->groups(['api', 'storage']) - ->label('scope', 'files.write') - ->label('event', 'storage.files.delete') - ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT]) - ->label('sdk.namespace', 'storage') - ->label('sdk.method', 'deleteFile') - ->label('sdk.description', '/docs/references/storage/delete-file.md') - ->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT) - ->label('sdk.response.model', Response::MODEL_NONE) - ->param('fileId', '', new UID(), 'File unique ID.') - ->inject('response') - ->inject('dbForInternal') - ->inject('events') - ->inject('audits') - ->inject('usage') - ->action(function ($fileId, $response, $dbForInternal, $events, $audits, $usage) { - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Database\Database $dbForInternal */ - /** @var Appwrite\Event\Event $events */ - /** @var Appwrite\Event\Event $audits */ - /** @var Appwrite\Event\Event $usage */ - - $file = $dbForInternal->getDocument('files', $fileId); - - if (empty($file->getId())) { - throw new Exception('File not found', 404); - } - - $device = Storage::getDevice('files'); - - if ($device->delete($file->getAttribute('path', ''))) { - if (!$dbForInternal->deleteDocument('files', $fileId)) { - throw new Exception('Failed to remove file from DB', 500); - } - } - - $audits - ->setParam('event', 'storage.files.delete') - ->setParam('resource', 'storage/files/'.$file->getId()) - ; - - $usage - ->setParam('storage', $file->getAttribute('size', 0) * -1) - ; - - $events - ->setParam('eventData', $response->output2($file, Response::MODEL_FILE)) - ; - - $response->noContent(); - }); diff --git a/composer.json b/composer.json index 0712b4b5ad..f0652c4403 100644 --- a/composer.json +++ b/composer.json @@ -38,7 +38,7 @@ "appwrite/php-clamav": "1.1.*", "appwrite/php-runtimes": "0.4.*", - "utopia-php/framework": "0.14.*", + "utopia-php/framework": "0.15.*", "utopia-php/abuse": "dev-feat-utopia-db-integration", "utopia-php/analytics": "0.2.*", "utopia-php/audit": "dev-feat-utopia-db-integration", diff --git a/composer.lock b/composer.lock index c0fbb00ce8..4553027855 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "ca8cf356c884e8d332b04bebf9d3b11b", + "content-hash": "14172d7cbfa5a5b7353d3f8b24e44b81", "packages": [ { "name": "adhocore/jwt", @@ -2079,16 +2079,16 @@ }, { "name": "utopia-php/framework", - "version": "0.14.1", + "version": "0.15.0", "source": { "type": "git", "url": "https://github.com/utopia-php/framework.git", - "reference": "632113288bebe41cbef79f0d355bd91609767b8c" + "reference": "3916871b173d6bc509c4e1bb236349382b8a84ab" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/framework/zipball/632113288bebe41cbef79f0d355bd91609767b8c", - "reference": "632113288bebe41cbef79f0d355bd91609767b8c", + "url": "https://api.github.com/repos/utopia-php/framework/zipball/3916871b173d6bc509c4e1bb236349382b8a84ab", + "reference": "3916871b173d6bc509c4e1bb236349382b8a84ab", "shasum": "" }, "require": { @@ -2122,9 +2122,9 @@ ], "support": { "issues": "https://github.com/utopia-php/framework/issues", - "source": "https://github.com/utopia-php/framework/tree/0.14.1" + "source": "https://github.com/utopia-php/framework/tree/0.15.0" }, - "time": "2021-05-21T06:41:45+00:00" + "time": "2021-07-04T12:55:46+00:00" }, { "name": "utopia-php/image", diff --git a/tests/e2e/Services/Storage/StorageBase.php b/tests/e2e/Services/Storage/StorageBase.php index bf1fd9b287..9f35e0fe0f 100644 --- a/tests/e2e/Services/Storage/StorageBase.php +++ b/tests/e2e/Services/Storage/StorageBase.php @@ -371,237 +371,4 @@ trait StorageBase return $data; } - public function testCreateFile(): array - { - /** - * Test for SUCCESS - */ - $file = $this->client->call(Client::METHOD_POST, '/storage/files', array_merge([ - 'content-type' => 'multipart/form-data', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'file' => new CURLFile(realpath(__DIR__ . '/../../../resources/logo.png'), 'image/png', 'logo.png'), - 'read' => ['role:all'], - 'write' => ['role:all'], - ]); - - $this->assertEquals($file['headers']['status-code'], 201); - $this->assertNotEmpty($file['body']['$id']); - $this->assertIsInt($file['body']['dateCreated']); - $this->assertEquals('logo.png', $file['body']['name']); - $this->assertEquals('image/png', $file['body']['mimeType']); - $this->assertEquals(47218, $file['body']['sizeOriginal']); - - /** - * Test for FAILURE - */ - return ['fileId' => $file['body']['$id']]; - } - - /** - * @depends testCreateFile - */ - public function testGetFile(array $data): array - { - /** - * Test for SUCCESS - */ - $file1 = $this->client->call(Client::METHOD_GET, '/storage/files/' . $data['fileId'], array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $this->assertEquals($file1['headers']['status-code'], 200); - $this->assertNotEmpty($file1['body']['$id']); - $this->assertIsInt($file1['body']['dateCreated']); - $this->assertEquals('logo.png', $file1['body']['name']); - $this->assertEquals('image/png', $file1['body']['mimeType']); - $this->assertEquals(47218, $file1['body']['sizeOriginal']); - //$this->assertEquals(54944, $file1['body']['sizeActual']); - //$this->assertEquals('gzip', $file1['body']['algorithm']); - //$this->assertEquals('1', $file1['body']['fileOpenSSLVersion']); - //$this->assertEquals('aes-128-gcm', $file1['body']['fileOpenSSLCipher']); - //$this->assertNotEmpty($file1['body']['fileOpenSSLTag']); - //$this->assertNotEmpty($file1['body']['fileOpenSSLIV']); - $this->assertIsArray($file1['body']['$read']); - $this->assertIsArray($file1['body']['$write']); - $this->assertCount(1, $file1['body']['$read']); - $this->assertCount(1, $file1['body']['$write']); - - $file2 = $this->client->call(Client::METHOD_GET, '/storage/files/' . $data['fileId'] . '/preview', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $this->assertEquals(200, $file2['headers']['status-code']); - $this->assertEquals('image/png', $file2['headers']['content-type']); - $this->assertNotEmpty($file2['body']); - - //new image preview features - $file3 = $this->client->call(Client::METHOD_GET, '/storage/files/' . $data['fileId'] . '/preview', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'width' => 300, - 'height' => 100, - 'borderRadius' => '50', - 'opacity' => '0.5', - 'output' => 'png', - 'rotation' => '45', - ]); - - $this->assertEquals(200, $file3['headers']['status-code']); - $this->assertEquals('image/png', $file3['headers']['content-type']); - $this->assertNotEmpty($file3['body']); - - $image = new \Imagick(); - $image->readImageBlob($file3['body']); - $original = new \Imagick(__DIR__ . '/../../../resources/logo-after.png'); - - $this->assertEquals($image->getImageWidth(), $original->getImageWidth()); - $this->assertEquals($image->getImageHeight(), $original->getImageHeight()); - $this->assertEquals('PNG', $image->getImageFormat()); - - $file4 = $this->client->call(Client::METHOD_GET, '/storage/files/' . $data['fileId'] . '/preview', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'width' => 200, - 'height' => 80, - 'borderWidth' => '5', - 'borderColor' => 'ff0000', - 'output' => 'jpg', - ]); - - $this->assertEquals(200, $file4['headers']['status-code']); - $this->assertEquals('image/jpeg', $file4['headers']['content-type']); - $this->assertNotEmpty($file4['body']); - - $image = new \Imagick(); - $image->readImageBlob($file4['body']); - $original = new \Imagick(__DIR__ . '/../../../resources/logo-after.jpg'); - - $this->assertEquals($image->getImageWidth(), $original->getImageWidth()); - $this->assertEquals($image->getImageHeight(), $original->getImageHeight()); - $this->assertEquals('JPEG', $image->getImageFormat()); - - $file5 = $this->client->call(Client::METHOD_GET, '/storage/files/' . $data['fileId'] . '/download', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $this->assertEquals(200, $file5['headers']['status-code']); - $this->assertEquals('attachment; filename="logo.png"', $file5['headers']['content-disposition']); - $this->assertEquals('image/png', $file5['headers']['content-type']); - $this->assertNotEmpty($file5['body']); - - $file6 = $this->client->call(Client::METHOD_GET, '/storage/files/' . $data['fileId'] . '/view', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $this->assertEquals(200, $file6['headers']['status-code']); - $this->assertEquals('image/png', $file6['headers']['content-type']); - $this->assertNotEmpty($file6['body']); - - /** - * Test for FAILURE - */ - - return $data; - } - - /** - * @depends testGetFile - */ - public function testListFiles(array $data): array - { - /** - * Test for SUCCESS - */ - $files = $this->client->call(Client::METHOD_GET, '/storage/files', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $this->assertEquals(200, $files['headers']['status-code']); - $this->assertGreaterThan(0, $files['body']['sum']); - $this->assertGreaterThan(0, count($files['body']['files'])); - - /** - * Test for FAILURE - */ - - return $data; - } - - /** - * @depends testListFiles - */ - public function testUpdateFile(array $data): array - { - /** - * Test for SUCCESS - */ - $file = $this->client->call(Client::METHOD_PUT, '/storage/files/' . $data['fileId'], array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'read' => ['role:all'], - 'write' => ['role:all'], - ]); - - $this->assertEquals(200, $file['headers']['status-code']); - $this->assertNotEmpty($file['body']['$id']); - $this->assertIsInt($file['body']['dateCreated']); - $this->assertEquals('logo.png', $file['body']['name']); - $this->assertEquals('image/png', $file['body']['mimeType']); - $this->assertEquals(47218, $file['body']['sizeOriginal']); - //$this->assertEquals(54944, $file['body']['sizeActual']); - //$this->assertEquals('gzip', $file['body']['algorithm']); - //$this->assertEquals('1', $file['body']['fileOpenSSLVersion']); - //$this->assertEquals('aes-128-gcm', $file['body']['fileOpenSSLCipher']); - //$this->assertNotEmpty($file['body']['fileOpenSSLTag']); - //$this->assertNotEmpty($file['body']['fileOpenSSLIV']); - $this->assertIsArray($file['body']['$read']); - $this->assertIsArray($file['body']['$write']); - $this->assertCount(1, $file['body']['$read']); - $this->assertCount(1, $file['body']['$write']); - - /** - * Test for FAILURE - */ - - return $data; - } - - /** - * @depends testUpdateFile - */ - public function testDeleteFile(array $data): array - { - /** - * Test for SUCCESS - */ - $file = $this->client->call(Client::METHOD_DELETE, '/storage/files/' . $data['fileId'], array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $this->assertEquals(204, $file['headers']['status-code']); - $this->assertEmpty($file['body']); - - $file = $this->client->call(Client::METHOD_GET, '/storage/files/' . $data['fileId'], array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $this->assertEquals(404, $file['headers']['status-code']); - - /** - * Test for FAILURE - */ - - return $data; - } }