diff --git a/app/config/collections.php b/app/config/collections.php index 25023cbcc6..f69651ca33 100644 --- a/app/config/collections.php +++ b/app/config/collections.php @@ -1436,97 +1436,6 @@ $collections = [ ], ], ], - Database::SYSTEM_COLLECTION_BUCKETS => [ - '$collection' => Database::SYSTEM_COLLECTION_COLLECTIONS, - '$id' => Database::SYSTEM_COLLECTION_BUCKETS, - '$permissions' => ['read' => ['*']], - 'name' => 'File', - 'structure' => true, - 'rules' => [ - [ - '$collection' => Database::SYSTEM_COLLECTION_RULES, - 'label' => 'Date Created', - 'key' => 'dateCreated', - 'type' => Database::SYSTEM_VAR_TYPE_NUMERIC, - 'default' => 0, - 'required' => false, - 'array' => false, - ], - [ - '$collection' => Database::SYSTEM_COLLECTION_RULES, - 'label' => 'Enabled', - 'key' => 'enabled', - 'type' => Database::SYSTEM_VAR_TYPE_BOOLEAN, - 'default' => true, - 'required' => false, - 'array' => false, - ], - [ - '$collection' => Database::SYSTEM_COLLECTION_RULES, - 'label' => 'Name', - 'key' => 'name', - 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - 'default' => '', - 'required' => true, - 'array' => false, - ], - [ - '$collection' => Database::SYSTEM_COLLECTION_RULES, - 'label' => 'Adapter', - 'key' => 'adapter', - 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - 'default' => '', - 'required' => true, - 'array' => false, - ], - [ - '$collection' => Database::SYSTEM_COLLECTION_RULES, - 'label' => 'Adapter Crednetials', - 'key' => 'adapterCredentials', - 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - 'default' => '', - 'required' => false, - 'array' => false, - 'filter' => ['json'] - ], - [ - '$collection' => Database::SYSTEM_COLLECTION_RULES, - 'label' => 'Maximum File Size', - 'key' => 'maximumFileSize', - 'type' => Database::SYSTEM_VAR_TYPE_NUMERIC, - 'default' => 0, - 'required' => true, - 'array' => false, - ], - [ - '$collection' => Database::SYSTEM_COLLECTION_RULES, - 'label' => 'Allowed File Extensions', - 'key' => 'allowedFileExtensions', - 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - 'default' => '', - 'required' => true, - 'array' => false, - ], - [ - '$collection' => Database::SYSTEM_COLLECTION_RULES, - 'label' => 'Encryption', - 'key' => 'encryption', - 'type' => Database::SYSTEM_VAR_TYPE_BOOLEAN, - 'default' => true, - 'required' => true, - 'array' => false, - ], - [ - '$collection' => Database::SYSTEM_COLLECTION_RULES, - 'label' => 'Virus Scan', - 'key' => 'antiVirus', - 'type' => Database::SYSTEM_VAR_TYPE_BOOLEAN, - 'default' => true, - 'required' => true, - 'array' => false, - ], - ], - ], Database::SYSTEM_COLLECTION_FUNCTIONS => [ '$collection' => Database::SYSTEM_COLLECTION_COLLECTIONS, '$id' => Database::SYSTEM_COLLECTION_FUNCTIONS, diff --git a/app/config/collections2.php b/app/config/collections2.php index b130092886..de3bcce83e 100644 --- a/app/config/collections2.php +++ b/app/config/collections2.php @@ -967,6 +967,125 @@ $collections = [ ] ], ], + 'buckets' => [ + '$collection' => Database::COLLECTIONS, + '$id' => 'buckets', + '$permissions' => ['read' => ['*']], + 'name' => 'Buckets', + 'structure' => true, + 'attributes' => [ + [ + '$id' => 'dateCreated', + 'type' => Database::VAR_INTEGER, + 'format' => '', + 'signed' => false, + 'size' => 0, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'dateUpdated', + 'type' => Database::VAR_INTEGER, + 'size' => 0, + 'format' => '', + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'enabled', + 'type' => Database::VAR_BOOLEAN, + 'signed' => true, + 'size' => 0, + 'format' => '', + 'filters' => [], + 'required' => true, + 'array' => false, + ], + [ + '$id' => 'name', + 'type' => Database::VAR_STRING, + 'signed' => true, + 'size' => 128, + 'format' => '', + 'filters' => [], + 'required' => true, + 'array' => false, + ], + [ + '$id' => 'adapter', + 'type' => Database::VAR_STRING, + 'signed' => true, + 'size' => 64, + 'format' => '', + 'filters' => [], + 'required' => true, + 'array' => false, + ], + [ + + '$id' => 'adapterCredentials', + 'type' => Database::VAR_STRING, + 'signed' => true, + 'size' => 16384, + 'format' => '', + 'required' => false, + 'array' => false, + 'filters' => ['json'] + ], + [ + '$id' => 'maximumFileSize', + 'type' => Database::VAR_INTEGER, + 'signed' => true, + 'size' => 0, + 'format' => '', + 'filters' => [], + 'required' => true, + 'array' => false, + ], + [ + '$id' => 'allowedFileExtensions', + 'type' => Database::VAR_STRING, + 'signed' => true, + 'size' => 64, + 'format' => '', + 'filters' => [], + 'required' => true, + 'array' => true, + ], + [ + '$id' => 'encryption', + 'type' => Database::VAR_BOOLEAN, + 'signed' => true, + 'size' => 0, + 'format' => '', + 'filters' => [], + 'required' => true, + 'array' => false, + ], + [ + '$id' => 'antiVirus', + 'type' => Database::VAR_BOOLEAN, + 'signed' => true, + 'size' => 0, + 'format' => '', + 'filters' => [], + 'required' => true, + 'array' => false, + ], + ], + 'indexes' => [ + [ + '$id' => '_fulltext_name', + 'type' => Database::INDEX_FULLTEXT, + 'attributes' => ['name'], + 'lengths' => [1024], + 'orders' => [Database::ORDER_ASC], + ] + ] + ] ]; /* diff --git a/app/config/events.php b/app/config/events.php index b27a5eafb9..18edf4ba4a 100644 --- a/app/config/events.php +++ b/app/config/events.php @@ -152,6 +152,11 @@ return [ 'model' => Response::MODEL_FILE, 'note' => '', ], + 'storage.buckets.create' => [ + 'description' => 'This event triggers when a storage bucket is created.', + 'model' => Response::MODEL_BUCKET, + 'note' => '', + ], 'users.create' => [ 'description' => 'This event triggers when a user is created from the users API.', 'model' => Response::MODEL_USER, diff --git a/app/config/roles.php b/app/config/roles.php index 3e06ddbfde..ae9157eaa7 100644 --- a/app/config/roles.php +++ b/app/config/roles.php @@ -13,6 +13,8 @@ $member = [ 'documents.write', 'files.read', 'files.write', + 'buckets.read', + 'buckets.write', 'projects.read', 'projects.write', 'locale.read', @@ -28,6 +30,8 @@ $admins = [ 'documents.write', 'files.read', 'files.write', + 'buckets.read', + 'buckets.write', 'users.read', 'users.write', 'collections.read', diff --git a/app/config/scopes.php b/app/config/scopes.php index 088502c5d8..303c834759 100644 --- a/app/config/scopes.php +++ b/app/config/scopes.php @@ -31,6 +31,12 @@ return [ // List of publicly visible scopes 'files.write' => [ 'description' => 'Access to create, update, and delete your project\'s storage files', ], + 'buckets.read' => [ + 'description' => 'Access to read your project\'s storage buckets', + ], + 'buckets.write' => [ + 'description' => 'Access to create, update, and delete your project\'s storage buckets', + ], 'functions.read' => [ 'description' => 'Access to read your project\'s functions and code tags', ], diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index a093f86cad..2191342102 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -6,6 +6,7 @@ use Utopia\Validator\ArrayList; use Utopia\Validator\WhiteList; use Utopia\Validator\Range; use Utopia\Validator\Text; +use Utopia\Validator\Boolean; use Utopia\Validator\HexColor; use Utopia\Cache\Cache; use Utopia\Cache\Adapter\Filesystem; @@ -21,8 +22,121 @@ use Utopia\Image\Image; use Appwrite\OpenSSL\OpenSSL; use Appwrite\Utopia\Response; use Utopia\Config\Config; +use Utopia\Validator\Integer; use Utopia\Database\Query; -use Utopia\Validator\Numeric; + +App::post('/v1/storage/buckets') + ->desc('Create storage bucket') + ->groups(['api', 'storage']) + ->label('scope', 'buckets.write') + ->label('event', 'storage.buckets.create') + ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) + ->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) + ->param('name', '', new Text(128), 'Bucket name', false) + ->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.', true) + ->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.', true) + ->param('maximumFileSize', 0, new Integer(), 'Maximum file size allowed.', true) + ->param('allowedFileExtensions', ['*'], new ArrayList(new Text(64)), 'Allowed file extensions', true) + ->param('enabled', true, new Boolean(), 'Is bucket enabled?', true) + ->param('adapter', 'local', new WhiteList(['local']), 'Storage adapter.', true) + ->param('encryption', true, new Boolean(), 'Is encryption enabled?', true) + ->param('antiVirus', true, new Boolean(), 'Is virus scanning enabled?', true) + ->inject('response') + ->inject('dbForInternal') + ->inject('user') + ->inject('audits') + ->action(function ($name, $read, $write, $maximumFileSize, $allowedFileExtensions, $enabled, $adapter, $encryption, $antiVirus, $response, $dbForInternal, $user, $audits) { + /** @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 */ + + $data = $dbForInternal->createDocument('buckets', new Document([ + '$collection' => 'buckets', + 'dateCreated' => \time(), + 'dateUpdated' => \time(), + 'name' => $name, + 'maximumFileSize' => $maximumFileSize, + 'allowedFileExtensions' => $allowedFileExtensions, + 'enabled' => $enabled, + 'adapter' => $adapter, + 'encryption' => $encryption, + 'antiVirus' => $antiVirus, + '$read' => $read, + '$write' => $write, + ])); + + $audits + ->setParam('event', 'storage.buckets.create') + ->setParam('resource', 'storage/buckets/' . $data->getId()) + ->setParam('data', $data->getArrayCopy()) + ; + + $response->setStatusCode(Response::STATUS_CODE_CREATED); + $response->dynamic2($data, Response::MODEL_BUCKET); + }); + +App::get('/v1/storage/buckets') + ->desc('List buckets') + ->groups(['api', 'storage']) + ->label('scope', 'buckets.read') + ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) + ->label('sdk.namespace', 'storage') + ->label('sdk.method', 'listBuckets') + ->label('sdk.description', '/docs/references/storage/list-buckets.md') + ->label('sdk.response.code', Response::STATUS_CODE_OK) + ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) + ->label('sdk.response.model', Response::MODEL_BUCKET_LIST) + ->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true) + ->param('limit', 25, new Range(0, 100), 'Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true) + ->param('offset', 0, new Range(0, 2000), 'Results offset. The default value is 0. Use this param to manage pagination.', true) + ->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([ + 'buckets' => $dbForInternal->find('buckets', $queries, $limit, $offset, ['_id'], [$orderType]), + 'sum' => $dbForInternal->count('buckets', $queries, APP_LIMIT_COUNT), + ]), Response::MODEL_BUCKET_LIST); + }); + +App::get('/v1/storage/buckets/:bucketId') + ->desc('Get Bucket') + ->groups(['api', 'storage']) + ->label('scope', 'buckets.read') + ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) + ->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') + ->inject('dbForInternal') + ->action(function ($bucketId, $response, $dbForInternal) { + /** @var Appwrite\Utopia\Response $response */ + /** @var Utopia\Database\Database $dbForInternal */ + + $bucket = $dbForInternal->getDocument('buckets', $bucketId); + + if ($bucket->isEmpty()) { + throw new Exception('Bucket not found', 404); + } + + $response->dynamic2($bucket, Response::MODEL_BUCKET); + }); App::post('/v1/storage/files') ->desc('Create File') @@ -586,4 +700,4 @@ App::delete('/v1/storage/files/:fileId') ; $response->noContent(); - }); \ No newline at end of file + }); diff --git a/composer.lock b/composer.lock index 20e5ea98ae..9d12880f2c 100644 --- a/composer.lock +++ b/composer.lock @@ -1704,7 +1704,7 @@ "source": { "type": "git", "url": "https://github.com/lohanidamodar/audit", - "reference": "b3ca9fa928fdec8c966596cbd85ee1141c79b6eb" + "reference": "d5591321161b81043c45be3c53ce9dcfa2d81d72" }, "require": { "ext-pdo": "*", @@ -1738,7 +1738,7 @@ "upf", "utopia" ], - "time": "2021-06-13T07:41:15+00:00" + "time": "2021-06-14T07:38:18+00:00" }, { "name": "utopia-php/cache", @@ -6195,5 +6195,5 @@ "platform-overrides": { "php": "8.0" }, - "plugin-api-version": "2.0.0" + "plugin-api-version": "2.1.0" } diff --git a/docs/references/storage/create-bucket.md b/docs/references/storage/create-bucket.md new file mode 100644 index 0000000000..0ac8f1bf5b --- /dev/null +++ b/docs/references/storage/create-bucket.md @@ -0,0 +1 @@ +Create a new storage bucket. \ No newline at end of file diff --git a/docs/references/storage/get-bucket.md b/docs/references/storage/get-bucket.md new file mode 100644 index 0000000000..e0bd37ceac --- /dev/null +++ b/docs/references/storage/get-bucket.md @@ -0,0 +1 @@ +Get a storage bucket by its unique ID. This endpoint response returns a JSON object with the storage bucket metadata. \ No newline at end of file diff --git a/docs/references/storage/list-buckets.md b/docs/references/storage/list-buckets.md new file mode 100644 index 0000000000..ed281ed277 --- /dev/null +++ b/docs/references/storage/list-buckets.md @@ -0,0 +1 @@ +Get a list of all the storage buckets. You can use the query params to filter your results. \ No newline at end of file diff --git a/src/Appwrite/Utopia/Response.php b/src/Appwrite/Utopia/Response.php index b158400265..7c454cd9a0 100644 --- a/src/Appwrite/Utopia/Response.php +++ b/src/Appwrite/Utopia/Response.php @@ -21,6 +21,7 @@ use Appwrite\Utopia\Response\Model\Error; use Appwrite\Utopia\Response\Model\ErrorDev; use Appwrite\Utopia\Response\Model\Execution; use Appwrite\Utopia\Response\Model\File; +use Appwrite\Utopia\Response\Model\Bucket; use Appwrite\Utopia\Response\Model\Func; use Appwrite\Utopia\Response\Model\JWT; use Appwrite\Utopia\Response\Model\Key; @@ -186,6 +187,7 @@ class Response extends SwooleResponse ->setModel(new JWT()) ->setModel(new Locale()) ->setModel(new File()) + ->setModel(new Bucket()) ->setModel(new Team()) ->setModel(new Membership()) ->setModel(new Func()) diff --git a/src/Appwrite/Utopia/Response/Model/Bucket.php b/src/Appwrite/Utopia/Response/Model/Bucket.php index e64d4d3b9a..c18d540685 100644 --- a/src/Appwrite/Utopia/Response/Model/Bucket.php +++ b/src/Appwrite/Utopia/Response/Model/Bucket.php @@ -17,16 +17,29 @@ class Bucket extends Model 'default' => '', 'example' => '5e5ea5c16897e', ]) - ->addRule('$permissions', [ - 'type' => Response::MODEL_PERMISSIONS, - 'description' => 'File permissions.', - 'default' => new \stdClass, - 'example' => new \stdClass, - 'array' => false, + ->addRule('$read', [ + 'type' => self::TYPE_STRING, + 'description' => 'File read permissions.', + 'default' => [], + 'example' => ['role:all'], + 'array' => true, + ]) + ->addRule('$write', [ + 'type' => self::TYPE_STRING, + 'description' => 'File write permissions.', + 'default' => [], + 'example' => ['user:608f9da25e7e1'], + 'array' => true, ]) ->addRule('dateCreated', [ 'type' => self::TYPE_INTEGER, - 'description' => 'File creation date in Unix timestamp.', + 'description' => 'Bucket creation date in Unix timestamp.', + 'default' => 0, + 'example' => 1592981250, + ]) + ->addRule('dateUpdated', [ + 'type' => self::TYPE_INTEGER, + 'description' => 'Bucket update date in Unix timestamp.', 'default' => 0, 'example' => 1592981250, ]) diff --git a/tests/e2e/Scopes/ProjectCustom.php b/tests/e2e/Scopes/ProjectCustom.php index 3f80285282..53586162c5 100644 --- a/tests/e2e/Scopes/ProjectCustom.php +++ b/tests/e2e/Scopes/ProjectCustom.php @@ -73,6 +73,8 @@ trait ProjectCustom 'documents.write', 'files.read', 'files.write', + 'buckets.read', + 'buckets.write', 'functions.read', 'functions.write', 'execution.read', @@ -124,6 +126,7 @@ trait ProjectCustom 'storage.files.create', 'storage.files.update', 'storage.files.delete', + 'storage.buckets.create', 'users.create', 'users.update.prefs', 'users.update.status', diff --git a/tests/e2e/Services/Storage/StorageCustomServerTest.php b/tests/e2e/Services/Storage/StorageCustomServerTest.php index 38bb3791be..87fa92836e 100644 --- a/tests/e2e/Services/Storage/StorageCustomServerTest.php +++ b/tests/e2e/Services/Storage/StorageCustomServerTest.php @@ -5,10 +5,110 @@ namespace Tests\E2E\Services\Storage; use Tests\E2E\Scopes\ProjectCustom; use Tests\E2E\Scopes\Scope; use Tests\E2E\Scopes\SideServer; +use Tests\E2E\Client; class StorageCustomServerTest extends Scope { use StorageBase; use ProjectCustom; use SideServer; + + public function testCreateBucket():array + { + /** + * Test for SUCCESS + */ + $bucket = $this->client->call(Client::METHOD_POST, '/storage/buckets', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'name' => 'Test Bucket', + ]); + $this->assertEquals(201, $bucket['headers']['status-code']); + $this->assertNotEmpty($bucket['body']['$id']); + $this->assertIsInt($bucket['body']['dateCreated']); + $this->assertIsArray($bucket['body']['$read']); + $this->assertIsArray($bucket['body']['$write']); + $this->assertIsArray($bucket['body']['allowedFileExtensions']); + $this->assertEquals('Test Bucket', $bucket['body']['name']); + $this->assertEquals(true, $bucket['body']['enabled']); + $this->assertEquals(true, $bucket['body']['encryption']); + $this->assertEquals(true, $bucket['body']['antiVirus']); + $this->assertEquals('local', $bucket['body']['adapter']); + $bucketId = $bucket['body']['$id']; + /** + * Test for FAILURE + */ + $bucket = $this->client->call(Client::METHOD_POST, '/storage/buckets', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'name' => '', + ]); + $this->assertEquals(400, $bucket['headers']['status-code']); + + return ['bucketId' => $bucketId]; + } + + /** + * @depends testCreateBucket + */ + public function testListBucket($data): array + { + $id = $data['bucketId'] ?? ''; + /** + * Test for SUCCESS + */ + $response = $this->client->call(Client::METHOD_GET, '/storage/buckets', + array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertNotEmpty($response['body']); + $this->assertEquals($id, $response['body']['buckets'][0]['$id']); + $this->assertEquals('Test Bucket', $response['body']['buckets'][0]['name']); + + return $data; + } + + /** + * @depends testCreateBucket + */ + public function testGetBucket($data): array + { + $id = $data['bucketId'] ?? ''; + /** + * Test for SUCCESS + */ + $response = $this->client->call(Client::METHOD_GET, '/storage/buckets/' . $id, + array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertNotEmpty($response['body']); + $this->assertEquals($id, $response['body']['$id']); + $this->assertEquals('Test Bucket', $response['body']['name']); + + /** + * Test for FAILURE + */ + + $response = $this->client->call(Client::METHOD_GET, '/storage/buckets/empty', + array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + $this->assertEquals(404, $response['headers']['status-code']); + + $response = $this->client->call(Client::METHOD_GET, '/storage/buckets/id-is-really-long-id-is-really-long-id-is-really-long-id-is-really-long', + array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + $this->assertEquals(400, $response['headers']['status-code']); + + return $data; + } } \ No newline at end of file diff --git a/tests/e2e/Services/Webhooks/WebhooksBase.php b/tests/e2e/Services/Webhooks/WebhooksBase.php index dfbdc524c7..a6f3bc2785 100644 --- a/tests/e2e/Services/Webhooks/WebhooksBase.php +++ b/tests/e2e/Services/Webhooks/WebhooksBase.php @@ -552,4 +552,39 @@ trait WebhooksBase */ return []; } + + public function testCreateStorageBucket(): array + { + /** + * Test for SUCCESS + */ + $bucket = $this->client->call(Client::METHOD_POST, '/storage/buckets', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'name' => 'Test Bucket', + ]); + + $this->assertEquals($bucket['headers']['status-code'], 201); + $this->assertNotEmpty($bucket['body']['$id']); + + $webhook = $this->getLastRequest(); + + $this->assertEquals($webhook['method'], 'POST'); + $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); + $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'storage.buckets.create'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); + $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-User-Id'] ?? ''), true); + $this->assertNotEmpty($webhook['data']['$id']); + $this->assertEquals('Test Bucket', $webhook['data']['name']); + $this->assertEquals(true, $webhook['data']['enabled']); + $this->assertIsArray($webhook['data']['$read']); + $this->assertIsArray($webhook['data']['$write']); + + return array_merge(['bucketId' => $bucket['body']['$id']]); + } } \ No newline at end of file