diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php index 8e3e0a411a..8781f6ab0d 100644 --- a/app/controllers/api/database.php +++ b/app/controllers/api/database.php @@ -50,7 +50,7 @@ use Appwrite\Detector\Detector; */ function createAttribute($collectionId, $attribute, $response, $dbForInternal, $dbForExternal, $database, $audits, $usage): Document { - $attributeId = $attribute->getId(); + $key = $attribute->getAttribute('key'); $type = $attribute->getAttribute('type', ''); $size = $attribute->getAttribute('size', 0); $required = $attribute->getAttribute('required', true); @@ -59,7 +59,7 @@ function createAttribute($collectionId, $attribute, $response, $dbForInternal, $ $format = $attribute->getAttribute('format', ''); $formatOptions = $attribute->getAttribute('formatOptions', []); $filters = $attribute->getAttribute('filters', []); // filters are hidden from the endpoint - $default = $attribute->getAttribute('default', null); + $default = $attribute->getAttribute('default'); $collection = $dbForInternal->getDocument('collections', $collectionId); @@ -84,8 +84,8 @@ function createAttribute($collectionId, $attribute, $response, $dbForInternal, $ try { $attribute = new Document([ - '$id' => $collectionId.'_'.$attributeId, - 'key' => $attributeId, + '$id' => $collectionId.'_'.$key, + 'key' => $key, 'collectionId' => $collectionId, 'type' => $type, 'status' => 'processing', // processing, available, failed, deleting, stuck @@ -711,7 +711,7 @@ App::post('/v1/database/collections/:collectionId/attributes/string') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_ATTRIBUTE_STRING) ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).') - ->param('attributeId', '', new Key(), 'Attribute ID.') + ->param('key', '', new Key(), 'Attribute Key.') ->param('size', null, new Range(1, APP_DATABASE_ATTRIBUTE_STRING_MAX_LENGTH, Range::TYPE_INTEGER), 'Attribute size for text attributes, in number of characters.') ->param('required', null, new Boolean(), 'Is attribute required?') ->param('default', null, new Text(0), 'Default value for attribute when not provided. Cannot be set when attribute is required.', true) @@ -722,7 +722,7 @@ App::post('/v1/database/collections/:collectionId/attributes/string') ->inject('database') ->inject('audits') ->inject('usage') - ->action(function ($collectionId, $attributeId, $size, $required, $default, $array, $response, $dbForInternal, $dbForExternal, $database, $audits, $usage) { + ->action(function ($collectionId, $key, $size, $required, $default, $array, $response, $dbForInternal, $dbForExternal, $database, $audits, $usage) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForInternal*/ /** @var Utopia\Database\Database $dbForExternal*/ @@ -737,7 +737,7 @@ App::post('/v1/database/collections/:collectionId/attributes/string') } $attribute = createAttribute($collectionId, new Document([ - '$id' => $attributeId, + 'key' => $key, 'type' => Database::VAR_STRING, 'size' => $size, 'required' => $required, @@ -761,7 +761,7 @@ App::post('/v1/database/collections/:collectionId/attributes/email') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_ATTRIBUTE_EMAIL) ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).') - ->param('attributeId', '', new Key(), 'Attribute ID.') + ->param('key', '', new Key(), 'Attribute Key.') ->param('required', null, new Boolean(), 'Is attribute required?') ->param('default', null, new Email(), 'Default value for attribute when not provided. Cannot be set when attribute is required.', true) ->param('array', false, new Boolean(), 'Is attribute an array?', true) @@ -771,7 +771,7 @@ App::post('/v1/database/collections/:collectionId/attributes/email') ->inject('database') ->inject('audits') ->inject('usage') - ->action(function ($collectionId, $attributeId, $required, $default, $array, $response, $dbForInternal, $dbForExternal, $database, $audits, $usage) { + ->action(function ($collectionId, $key, $required, $default, $array, $response, $dbForInternal, $dbForExternal, $database, $audits, $usage) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForInternal*/ /** @var Utopia\Database\Database $dbForExternal*/ @@ -780,7 +780,7 @@ App::post('/v1/database/collections/:collectionId/attributes/email') /** @var Appwrite\Stats\Stats $usage */ $attribute = createAttribute($collectionId, new Document([ - '$id' => $attributeId, + 'key' => $key, 'type' => Database::VAR_STRING, 'size' => 254, 'required' => $required, @@ -805,7 +805,7 @@ App::post('/v1/database/collections/:collectionId/attributes/enum') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_ATTRIBUTE_ENUM) ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).') - ->param('attributeId', '', new Key(), 'Attribute ID.') + ->param('key', '', new Key(), 'Attribute Key.') ->param('elements', [], new ArrayList(new Text(0)), 'Array of elements in enumerated type. Uses length of longest element to determine size.') ->param('required', null, new Boolean(), 'Is attribute required?') ->param('default', null, new Text(0), 'Default value for attribute when not provided. Cannot be set when attribute is required.', true) @@ -816,7 +816,7 @@ App::post('/v1/database/collections/:collectionId/attributes/enum') ->inject('database') ->inject('audits') ->inject('usage') - ->action(function ($collectionId, $attributeId, $elements, $required, $default, $array, $response, $dbForInternal, $dbForExternal, $database, $audits, $usage) { + ->action(function ($collectionId, $key, $elements, $required, $default, $array, $response, $dbForInternal, $dbForExternal, $database, $audits, $usage) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForInternal*/ /** @var Utopia\Database\Database $dbForExternal*/ @@ -836,7 +836,7 @@ App::post('/v1/database/collections/:collectionId/attributes/enum') } $attribute = createAttribute($collectionId, new Document([ - '$id' => $attributeId, + 'key' => $key, 'type' => Database::VAR_STRING, 'size' => $size, 'required' => $required, @@ -862,7 +862,7 @@ App::post('/v1/database/collections/:collectionId/attributes/ip') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_ATTRIBUTE_IP) ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).') - ->param('attributeId', '', new Key(), 'Attribute ID.') + ->param('key', '', new Key(), 'Attribute Key.') ->param('required', null, new Boolean(), 'Is attribute required?') ->param('default', null, new IP(), 'Default value for attribute when not provided. Cannot be set when attribute is required.', true) ->param('array', false, new Boolean(), 'Is attribute an array?', true) @@ -872,7 +872,7 @@ App::post('/v1/database/collections/:collectionId/attributes/ip') ->inject('database') ->inject('audits') ->inject('usage') - ->action(function ($collectionId, $attributeId, $required, $default, $array, $response, $dbForInternal, $dbForExternal, $database, $audits, $usage) { + ->action(function ($collectionId, $key, $required, $default, $array, $response, $dbForInternal, $dbForExternal, $database, $audits, $usage) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForInternal*/ /** @var Utopia\Database\Database $dbForExternal*/ @@ -881,7 +881,7 @@ App::post('/v1/database/collections/:collectionId/attributes/ip') /** @var Appwrite\Stats\Stats $usage */ $attribute = createAttribute($collectionId, new Document([ - '$id' => $attributeId, + 'key' => $key, 'type' => Database::VAR_STRING, 'size' => 39, 'required' => $required, @@ -906,7 +906,7 @@ App::post('/v1/database/collections/:collectionId/attributes/url') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_ATTRIBUTE_URL) ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).') - ->param('attributeId', '', new Key(), 'Attribute ID.') + ->param('key', '', new Key(), 'Attribute Key.') ->param('required', null, new Boolean(), 'Is attribute required?') ->param('default', null, new URL(), 'Default value for attribute when not provided. Cannot be set when attribute is required.', true) ->param('array', false, new Boolean(), 'Is attribute an array?', true) @@ -916,7 +916,7 @@ App::post('/v1/database/collections/:collectionId/attributes/url') ->inject('database') ->inject('audits') ->inject('usage') - ->action(function ($collectionId, $attributeId, $required, $default, $array, $response, $dbForInternal, $dbForExternal, $database, $audits, $usage) { + ->action(function ($collectionId, $key, $required, $default, $array, $response, $dbForInternal, $dbForExternal, $database, $audits, $usage) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForExternal*/ /** @var Appwrite\Event\Event $database */ @@ -924,7 +924,7 @@ App::post('/v1/database/collections/:collectionId/attributes/url') /** @var Appwrite\Stats\Stats $usage */ $attribute = createAttribute($collectionId, new Document([ - '$id' => $attributeId, + 'key' => $key, 'type' => Database::VAR_STRING, 'size' => 2000, 'required' => $required, @@ -949,7 +949,7 @@ App::post('/v1/database/collections/:collectionId/attributes/integer') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_ATTRIBUTE_INTEGER) ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).') - ->param('attributeId', '', new Key(), 'Attribute ID.') + ->param('key', '', new Key(), 'Attribute Key.') ->param('required', null, new Boolean(), 'Is attribute required?') ->param('min', null, new Integer(), 'Minimum value to enforce on new documents', true) ->param('max', null, new Integer(), 'Maximum value to enforce on new documents', true) @@ -961,7 +961,7 @@ App::post('/v1/database/collections/:collectionId/attributes/integer') ->inject('database') ->inject('audits') ->inject('usage') - ->action(function ($collectionId, $attributeId, $required, $min, $max, $default, $array, $response, $dbForInternal, $dbForExternal, $database, $audits, $usage) { + ->action(function ($collectionId, $key, $required, $min, $max, $default, $array, $response, $dbForInternal, $dbForExternal, $database, $audits, $usage) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForInternal*/ /** @var Utopia\Database\Database $dbForExternal*/ @@ -984,7 +984,7 @@ App::post('/v1/database/collections/:collectionId/attributes/integer') } $attribute = createAttribute($collectionId, new Document([ - '$id' => $attributeId, + 'key' => $key, 'type' => Database::VAR_INTEGER, 'size' => 0, 'required' => $required, @@ -1020,7 +1020,7 @@ App::post('/v1/database/collections/:collectionId/attributes/float') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_ATTRIBUTE_FLOAT) ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).') - ->param('attributeId', '', new Key(), 'Attribute ID.') + ->param('key', '', new Key(), 'Attribute Key.') ->param('required', null, new Boolean(), 'Is attribute required?') ->param('min', null, new FloatValidator(), 'Minimum value to enforce on new documents', true) ->param('max', null, new FloatValidator(), 'Maximum value to enforce on new documents', true) @@ -1032,7 +1032,7 @@ App::post('/v1/database/collections/:collectionId/attributes/float') ->inject('database') ->inject('audits') ->inject('usage') - ->action(function ($collectionId, $attributeId, $required, $min, $max, $default, $array, $response, $dbForInternal, $dbForExternal, $database, $audits, $usage) { + ->action(function ($collectionId, $key, $required, $min, $max, $default, $array, $response, $dbForInternal, $dbForExternal, $database, $audits, $usage) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForInternal*/ /** @var Utopia\Database\Database $dbForExternal*/ @@ -1047,7 +1047,7 @@ App::post('/v1/database/collections/:collectionId/attributes/float') if ($min > $max) { throw new Exception('Minimum value must be lesser than maximum value', 400); } - + // Ensure default value is a float if (!is_null($default)) { $default = \floatval($default); @@ -1060,7 +1060,7 @@ App::post('/v1/database/collections/:collectionId/attributes/float') } $attribute = createAttribute($collectionId, new Document([ - '$id' => $attributeId, + 'key' => $key, 'type' => Database::VAR_FLOAT, 'required' => $required, 'size' => 0, @@ -1096,7 +1096,7 @@ App::post('/v1/database/collections/:collectionId/attributes/boolean') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_ATTRIBUTE_BOOLEAN) ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).') - ->param('attributeId', '', new Key(), 'Attribute ID.') + ->param('key', '', new Key(), 'Attribute Key.') ->param('required', null, new Boolean(), 'Is attribute required?') ->param('default', null, new Boolean(), 'Default value for attribute when not provided. Cannot be set when attribute is required.', true) ->param('array', false, new Boolean(), 'Is attribute an array?', true) @@ -1106,7 +1106,7 @@ App::post('/v1/database/collections/:collectionId/attributes/boolean') ->inject('database') ->inject('audits') ->inject('usage') - ->action(function ($collectionId, $attributeId, $required, $default, $array, $response, $dbForInternal, $dbForExternal, $database, $audits, $usage) { + ->action(function ($collectionId, $key, $required, $default, $array, $response, $dbForInternal, $dbForExternal, $database, $audits, $usage) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForInternal*/ /** @var Appwrite\Event\Event $database */ @@ -1114,7 +1114,7 @@ App::post('/v1/database/collections/:collectionId/attributes/boolean') /** @var Appwrite\Stats\Stats $usage */ $attribute = createAttribute($collectionId, new Document([ - '$id' => $attributeId, + 'key' => $key, 'type' => Database::VAR_BOOLEAN, 'size' => 0, 'required' => $required, @@ -1160,7 +1160,7 @@ App::get('/v1/database/collections/:collectionId/attributes') ]), Response::MODEL_ATTRIBUTE_LIST); }); -App::get('/v1/database/collections/:collectionId/attributes/:attributeId') +App::get('/v1/database/collections/:collectionId/attributes/:key') ->desc('Get Attribute') ->groups(['api', 'database']) ->label('scope', 'collections.read') @@ -1180,11 +1180,11 @@ App::get('/v1/database/collections/:collectionId/attributes/:attributeId') Response::MODEL_ATTRIBUTE_IP, Response::MODEL_ATTRIBUTE_STRING,])// needs to be last, since its condition would dominate any other string attribute ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).') - ->param('attributeId', '', new Key(), 'Attribute ID.') + ->param('key', '', new Key(), 'Attribute Key.') ->inject('response') ->inject('dbForInternal') ->inject('usage') - ->action(function ($collectionId, $attributeId, $response, $dbForInternal, $usage) { + ->action(function ($collectionId, $key, $response, $dbForInternal, $usage) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForInternal */ @@ -1194,7 +1194,7 @@ App::get('/v1/database/collections/:collectionId/attributes/:attributeId') throw new Exception('Collection not found', 404); } - $attribute = $collection->find('$id', $attributeId, 'attributes'); + $attribute = $collection->find('$id', $key, 'attributes'); if (!$attribute) { throw new Exception('Attribute not found', 404); @@ -1223,7 +1223,7 @@ App::get('/v1/database/collections/:collectionId/attributes/:attributeId') $response->dynamic($attribute, $model); }); -App::delete('/v1/database/collections/:collectionId/attributes/:attributeId') +App::delete('/v1/database/collections/:collectionId/attributes/:key') ->desc('Delete Attribute') ->groups(['api', 'database']) ->label('scope', 'collections.write') @@ -1235,7 +1235,7 @@ App::delete('/v1/database/collections/:collectionId/attributes/:attributeId') ->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT) ->label('sdk.response.model', Response::MODEL_NONE) ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).') - ->param('attributeId', '', new Key(), 'Attribute ID.') + ->param('key', '', new Key(), 'Attribute Key.') ->inject('response') ->inject('dbForInternal') ->inject('dbForExternal') @@ -1243,7 +1243,7 @@ App::delete('/v1/database/collections/:collectionId/attributes/:attributeId') ->inject('events') ->inject('audits') ->inject('usage') - ->action(function ($collectionId, $attributeId, $response, $dbForInternal, $dbForExternal, $database, $events, $audits, $usage) { + ->action(function ($collectionId, $key, $response, $dbForInternal, $dbForExternal, $database, $events, $audits, $usage) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForInternal */ /** @var Utopia\Database\Database $dbForExternal */ @@ -1258,7 +1258,7 @@ App::delete('/v1/database/collections/:collectionId/attributes/:attributeId') throw new Exception('Collection not found', 404); } - $attribute = $dbForInternal->getDocument('attributes', $collectionId.'_'.$attributeId); + $attribute = $dbForInternal->getDocument('attributes', $collectionId.'_'.$key); if (empty($attribute->getId())) { throw new Exception('Attribute not found', 404); @@ -1324,7 +1324,7 @@ App::post('/v1/database/collections/:collectionId/indexes') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_INDEX) ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).') - ->param('indexId', null, new Key(), 'Index ID.') + ->param('key', null, new Key(), 'Index Key.') ->param('type', null, new WhiteList([Database::INDEX_KEY, Database::INDEX_FULLTEXT, Database::INDEX_UNIQUE, Database::INDEX_SPATIAL, Database::INDEX_ARRAY]), 'Index type.') ->param('attributes', null, new ArrayList(new Key()), 'Array of attributes to index.') ->param('orders', [], new ArrayList(new WhiteList(['ASC', 'DESC'], false, Database::VAR_STRING)), 'Array of index orders.', true) @@ -1333,7 +1333,7 @@ App::post('/v1/database/collections/:collectionId/indexes') ->inject('database') ->inject('audits') ->inject('usage') - ->action(function ($collectionId, $indexId, $type, $attributes, $orders, $response, $dbForInternal, $database, $audits, $usage) { + ->action(function ($collectionId, $key, $type, $attributes, $orders, $response, $dbForInternal, $database, $audits, $usage) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForInternal */ /** @var Appwrite\Event\Event $database */ @@ -1364,7 +1364,7 @@ App::post('/v1/database/collections/:collectionId/indexes') // lengths hidden by default $lengths = []; - foreach ($attributes as $key => $attribute) { + foreach ($attributes as $i => $attribute) { // find attribute metadata in collection document $attributeIndex = \array_search($attribute, array_column($oldAttributes, 'key')); @@ -1382,13 +1382,13 @@ App::post('/v1/database/collections/:collectionId/indexes') } // set attribute size as index length only for strings - $lengths[$key] = ($attributeType === Database::VAR_STRING) ? $attributeSize : null; + $lengths[$i] = ($attributeType === Database::VAR_STRING) ? $attributeSize : null; } try { $index = $dbForInternal->createDocument('indexes', new Document([ - '$id' => $collectionId.'_'.$indexId, - 'key' => $indexId, + '$id' => $collectionId.'_'.$key, + 'key' => $key, 'status' => 'processing', // processing, available, failed, deleting, stuck 'collectionId' => $collectionId, 'type' => $type, @@ -1455,7 +1455,7 @@ App::get('/v1/database/collections/:collectionId/indexes') ]), Response::MODEL_INDEX_LIST); }); -App::get('/v1/database/collections/:collectionId/indexes/:indexId') +App::get('/v1/database/collections/:collectionId/indexes/:key') ->desc('Get Index') ->groups(['api', 'database']) ->label('scope', 'collections.read') @@ -1467,11 +1467,11 @@ App::get('/v1/database/collections/:collectionId/indexes/:indexId') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_INDEX) ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).') - ->param('indexId', null, new Key(), 'Index ID.') + ->param('key', null, new Key(), 'Index Key.') ->inject('response') ->inject('dbForInternal') ->inject('usage') - ->action(function ($collectionId, $indexId, $response, $dbForInternal, $usage) { + ->action(function ($collectionId, $key, $response, $dbForInternal, $usage) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForInternal */ @@ -1484,7 +1484,7 @@ App::get('/v1/database/collections/:collectionId/indexes/:indexId') $indexes = $collection->getAttribute('indexes'); // Search for index - $indexIndex = array_search($indexId, array_column($indexes, '$id')); + $indexIndex = array_search($key, array_column($indexes, 'key')); if ($indexIndex === false) { throw new Exception('Index not found', 404); @@ -1495,11 +1495,11 @@ App::get('/v1/database/collections/:collectionId/indexes/:indexId') ])]); $usage->setParam('database.collections.read', 1); - + $response->dynamic($index, Response::MODEL_INDEX); }); -App::delete('/v1/database/collections/:collectionId/indexes/:indexId') +App::delete('/v1/database/collections/:collectionId/indexes/:key') ->desc('Delete Index') ->groups(['api', 'database']) ->label('scope', 'collections.write') @@ -1511,14 +1511,14 @@ App::delete('/v1/database/collections/:collectionId/indexes/:indexId') ->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT) ->label('sdk.response.model', Response::MODEL_NONE) ->param('collectionId', null, new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).') - ->param('indexId', '', new Key(), 'Index ID.') + ->param('key', '', new Key(), 'Index Key.') ->inject('response') ->inject('dbForInternal') ->inject('database') ->inject('events') ->inject('audits') ->inject('usage') - ->action(function ($collectionId, $indexId, $response, $dbForInternal, $database, $events, $audits, $usage) { + ->action(function ($collectionId, $key, $response, $dbForInternal, $database, $events, $audits, $usage) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForInternal */ /** @var Appwrite\Event\Event $database */ @@ -1532,7 +1532,7 @@ App::delete('/v1/database/collections/:collectionId/indexes/:indexId') throw new Exception('Collection not found', 404); } - $index = $dbForInternal->getDocument('indexes', $collectionId.'_'.$indexId); + $index = $dbForInternal->getDocument('indexes', $collectionId.'_'.$key); if (empty($index->getId())) { throw new Exception('Index not found', 404); @@ -1589,13 +1589,15 @@ App::post('/v1/database/collections/:collectionId/documents') ->inject('user') ->inject('audits') ->inject('usage') - ->action(function ($documentId, $collectionId, $data, $read, $write, $response, $dbForInternal, $dbForExternal, $user, $audits, $usage) { + ->inject('events') + ->action(function ($documentId, $collectionId, $data, $read, $write, $response, $dbForInternal, $dbForExternal, $user, $audits, $usage, $events) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForInternal */ /** @var Utopia\Database\Database $dbForExternal */ /** @var Utopia\Database\Document $user */ /** @var Appwrite\Event\Event $audits */ /** @var Appwrite\Stats\Stats $usage */ + /** @var Appwrite\Event\Event $events */ $data = (\is_string($data)) ? \json_decode($data, true) : $data; // Cast to JSON array @@ -1607,7 +1609,10 @@ App::post('/v1/database/collections/:collectionId/documents') throw new Exception('$id is not allowed for creating new documents, try update instead', 400); } - $collection = $dbForInternal->getDocument('collections', $collectionId); + /** + * Skip Authorization to get the collection. Needed in case of empty permissions for document level permissions. + */ + $collection = Authorization::skip(fn() => $dbForInternal->getDocument('collections', $collectionId)); if ($collection->isEmpty()) { throw new Exception('Collection not found', 404); @@ -1657,6 +1662,8 @@ App::post('/v1/database/collections/:collectionId/documents') throw new Exception('Document already exists', 409); } + $events->setParam('collection', $collection->getArrayCopy()); + $usage ->setParam('database.documents.create', 1) ->setParam('collectionId', $collectionId) @@ -1701,7 +1708,10 @@ App::get('/v1/database/collections/:collectionId/documents') /** @var Utopia\Database\Database $dbForExternal */ /** @var Appwrite\Stats\Stats $usage */ - $collection = $dbForInternal->getDocument('collections', $collectionId); + /** + * Skip Authorization to get the collection. Needed in case of empty permissions for document level permissions. + */ + $collection = Authorization::skip(fn() => $dbForInternal->getDocument('collections', $collectionId)); if ($collection->isEmpty()) { throw new Exception('Collection not found', 404); @@ -1738,8 +1748,10 @@ App::get('/v1/database/collections/:collectionId/documents') if ($collection->getAttribute('permission') === 'collection') { /** @var Document[] $documents */ $documents = Authorization::skip(fn() => $dbForExternal->find($collectionId, $queries, $limit, $offset, $orderAttributes, $orderTypes, $cursorDocument ?? null, $cursorDirection)); + $sum = Authorization::skip(fn() => $dbForExternal->count($collectionId, $queries, APP_LIMIT_COUNT)); } else { $documents = $dbForExternal->find($collectionId, $queries, $limit, $offset, $orderAttributes, $orderTypes, $cursorDocument ?? null, $cursorDirection); + $sum = $dbForExternal->count($collectionId, $queries, APP_LIMIT_COUNT); } $usage @@ -1748,7 +1760,7 @@ App::get('/v1/database/collections/:collectionId/documents') ; $response->dynamic(new Document([ - 'sum' => $dbForExternal->count($collectionId, $queries, APP_LIMIT_COUNT), + 'sum' => $sum, 'documents' => $documents, ]), Response::MODEL_DOCUMENT_LIST); }); @@ -1775,7 +1787,10 @@ App::get('/v1/database/collections/:collectionId/documents/:documentId') /** @var Utopia\Database\Database $$dbForInternal */ /** @var Utopia\Database\Database $dbForExternal */ - $collection = $dbForInternal->getDocument('collections', $collectionId); + /** + * Skip Authorization to get the collection. Needed in case of empty permissions for document level permissions. + */ + $collection = Authorization::skip(fn() => $dbForInternal->getDocument('collections', $collectionId)); if ($collection->isEmpty()) { throw new Exception('Collection not found', 404); @@ -1924,14 +1939,19 @@ App::patch('/v1/database/collections/:collectionId/documents/:documentId') ->inject('dbForExternal') ->inject('audits') ->inject('usage') - ->action(function ($collectionId, $documentId, $data, $read, $write, $response, $dbForInternal, $dbForExternal, $audits, $usage) { + ->inject('events') + ->action(function ($collectionId, $documentId, $data, $read, $write, $response, $dbForInternal, $dbForExternal, $audits, $usage, $events) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForInternal */ /** @var Utopia\Database\Database $dbForExternal */ /** @var Appwrite\Event\Event $audits */ /** @var Appwrite\Stats\Stats $usage */ + /** @var Appwrite\Event\Event $events */ - $collection = $dbForInternal->getDocument('collections', $collectionId); + /** + * Skip Authorization to get the collection. Needed in case of empty permissions for document level permissions. + */ + $collection = Authorization::skip(fn() => $dbForInternal->getDocument('collections', $collectionId)); if ($collection->isEmpty()) { throw new Exception('Collection not found', 404); @@ -1943,9 +1963,12 @@ App::patch('/v1/database/collections/:collectionId/documents/:documentId') if (!$validator->isValid($collection->getWrite())) { throw new Exception('Unauthorized permissions', 401); } + + $document = Authorization::skip(fn() => $dbForExternal->getDocument($collectionId, $documentId)); + } else { + $document = $dbForExternal->getDocument($collectionId, $documentId); } - $document = $dbForExternal->getDocument($collectionId, $documentId); if ($document->isEmpty()) { throw new Exception('Document not found', 404); @@ -2001,7 +2024,9 @@ App::patch('/v1/database/collections/:collectionId/documents/:documentId') catch (StructureException $exception) { throw new Exception($exception->getMessage(), 400); } - + + $events->setParam('collection', $collection->getArrayCopy()); + $usage ->setParam('database.documents.update', 1) ->setParam('collectionId', $collectionId) @@ -2042,7 +2067,10 @@ App::delete('/v1/database/collections/:collectionId/documents/:documentId') /** @var Appwrite\Event\Event $audits */ /** @var Appwrite\Stats\Stats $usage */ - $collection = $dbForInternal->getDocument('collections', $collectionId); + /** + * Skip Authorization to get the collection. Needed in case of empty permissions for document level permissions. + */ + $collection = Authorization::skip(fn() => $dbForInternal->getDocument('collections', $collectionId)); if ($collection->isEmpty()) { throw new Exception('Collection not found', 404); @@ -2067,7 +2095,12 @@ App::delete('/v1/database/collections/:collectionId/documents/:documentId') throw new Exception('No document found', 404); } - $dbForExternal->deleteDocument($collectionId, $documentId); + if ($collection->getAttribute('permission') === 'collection') { + Authorization::skip(fn() => $dbForExternal->deleteDocument($collectionId, $documentId)); + } else { + $dbForExternal->deleteDocument($collectionId, $documentId); + } + $dbForExternal->deleteCachedDocument($collectionId, $documentId); $usage @@ -2077,6 +2110,7 @@ App::delete('/v1/database/collections/:collectionId/documents/:documentId') $events ->setParam('eventData', $response->output($document, Response::MODEL_DOCUMENT)) + ->setParam('collection', $collection->getArrayCopy()); ; $audits diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index b1087f28e6..ef1e15a4d9 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -207,7 +207,14 @@ App::shutdown(function ($utopia, $request, $response, $project, $events, $audits if ($project->getId() !== 'console') { $payload = new Document($response->getPayload()); - $target = Realtime::fromPayload($events->getParam('event'), $payload, $project); + $collection = new Document($events->getParam('collection') ?? []); + + $target = Realtime::fromPayload( + event: $events->getParam('event'), + payload: $payload, + project: $project, + collection: $collection + ); Realtime::send( $target['projectId'] ?? $project->getId(), diff --git a/src/Appwrite/Messaging/Adapter/Realtime.php b/src/Appwrite/Messaging/Adapter/Realtime.php index 8fb3ad4aa5..59708ec084 100644 --- a/src/Appwrite/Messaging/Adapter/Realtime.php +++ b/src/Appwrite/Messaging/Adapter/Realtime.php @@ -240,7 +240,7 @@ class Realtime extends Adapter * @param Document|null $project * @return array */ - public static function fromPayload(string $event, Document $payload, Document $project = null): array + public static function fromPayload(string $event, Document $payload, Document $project = null, Document $collection = null): array { $channels = []; $roles = []; @@ -275,12 +275,6 @@ class Realtime extends Adapter $channels[] = 'teams.' . $payload->getId(); $roles = ['team:' . $payload->getId()]; - break; - case strpos($event, 'database.collections.') === 0: - $channels[] = 'collections'; - $channels[] = 'collections.' . $payload->getId(); - $roles = $payload->getRead(); - break; case strpos($event, 'database.attributes.') === 0: case strpos($event, 'database.indexes.') === 0: @@ -290,10 +284,15 @@ class Realtime extends Adapter break; case strpos($event, 'database.documents.') === 0: + if ($collection->isEmpty()) { + throw new \Exception('Collection need to be passed to to Realtime for Document events in the Database.'); + } + $channels[] = 'documents'; $channels[] = 'collections.' . $payload->getAttribute('$collection') . '.documents'; $channels[] = 'documents.' . $payload->getId(); - $roles = $payload->getRead(); + + $roles = ($collection->getAttribute('permission') === 'collection') ? $collection->getRead() : $payload->getRead(); break; case strpos($event, 'storage.') === 0: diff --git a/tests/e2e/Services/Database/DatabaseBase.php b/tests/e2e/Services/Database/DatabaseBase.php index 2f852d0f5e..8c111cd2cf 100644 --- a/tests/e2e/Services/Database/DatabaseBase.php +++ b/tests/e2e/Services/Database/DatabaseBase.php @@ -19,8 +19,8 @@ trait DatabaseBase ]), [ 'collectionId' => 'unique()', 'name' => 'Movies', - 'read' => ['role:all'], - 'write' => ['role:all'], + 'read' => [], + 'write' => [], 'permission' => 'document', ]); @@ -40,7 +40,7 @@ trait DatabaseBase 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'attributeId' => 'title', + 'key' => 'title', 'size' => 256, 'required' => true, ]); @@ -50,7 +50,7 @@ trait DatabaseBase 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'attributeId' => 'releaseYear', + 'key' => 'releaseYear', 'required' => true, ]); @@ -59,7 +59,7 @@ trait DatabaseBase 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'attributeId' => 'actors', + 'key' => 'actors', 'size' => 256, 'required' => false, 'array' => true, @@ -113,8 +113,8 @@ trait DatabaseBase ]), [ 'collectionId' => 'unique()', 'name' => 'Response Models', - 'read' => ['role:all'], - 'write' => ['role:all'], + 'read' => [], + 'write' => [], 'permission' => 'document', ]); @@ -128,7 +128,7 @@ trait DatabaseBase 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'attributeId' => 'string', + 'key' => 'string', 'size' => 16, 'required' => false, 'default' => 'default', @@ -139,7 +139,7 @@ trait DatabaseBase 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'attributeId' => 'email', + 'key' => 'email', 'required' => false, 'default' => 'default@example.com', ]); @@ -149,7 +149,7 @@ trait DatabaseBase 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'attributeId' => 'enum', + 'key' => 'enum', 'elements' => ['yes', 'no', 'maybe'], 'required' => false, 'default' => 'maybe', @@ -160,7 +160,7 @@ trait DatabaseBase 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'attributeId' => 'ip', + 'key' => 'ip', 'required' => false, 'default' => '192.0.2.0', ]); @@ -170,7 +170,7 @@ trait DatabaseBase 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'attributeId' => 'url', + 'key' => 'url', 'required' => false, 'default' => 'http://example.com', ]); @@ -180,7 +180,7 @@ trait DatabaseBase 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'attributeId' => 'integer', + 'key' => 'integer', 'required' => false, 'min' => 1, 'max' => 5, @@ -192,7 +192,7 @@ trait DatabaseBase 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'attributeId' => 'float', + 'key' => 'float', 'required' => false, 'min' => 1.5, 'max' => 5.5, @@ -204,7 +204,7 @@ trait DatabaseBase 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'attributeId' => 'boolean', + 'key' => 'boolean', 'required' => false, 'default' => true, ]); @@ -568,7 +568,7 @@ trait DatabaseBase 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'attributeId' => 'enum', + 'key' => 'enum', 'elements' => ['yes', 'no', ''], 'required' => false, 'default' => 'maybe', @@ -590,7 +590,7 @@ trait DatabaseBase 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'indexId' => 'titleIndex', + 'key' => 'titleIndex', 'type' => 'fulltext', 'attributes' => ['title'], ]); @@ -606,7 +606,7 @@ trait DatabaseBase 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'indexId' => 'releaseYear', + 'key' => 'releaseYear', 'type' => 'key', 'attributes' => ['releaseYear'], ]); @@ -1229,8 +1229,8 @@ trait DatabaseBase ]), [ 'collectionId' => 'unique()', 'name' => 'invalidDocumentStructure', - 'read' => ['role:all'], - 'write' => ['role:all'], + 'read' => [], + 'write' => [], 'permission' => 'document', ]); @@ -1244,7 +1244,7 @@ trait DatabaseBase 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'attributeId' => 'email', + 'key' => 'email', 'required' => false, ]); @@ -1253,7 +1253,7 @@ trait DatabaseBase 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'attributeId' => 'enum', + 'key' => 'enum', 'elements' => ['yes', 'no', 'maybe'], 'required' => false, ]); @@ -1263,7 +1263,7 @@ trait DatabaseBase 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'attributeId' => 'ip', + 'key' => 'ip', 'required' => false, ]); @@ -1272,7 +1272,7 @@ trait DatabaseBase 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'attributeId' => 'url', + 'key' => 'url', 'size' => 256, 'required' => false, ]); @@ -1282,7 +1282,7 @@ trait DatabaseBase 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'attributeId' => 'range', + 'key' => 'range', 'required' => false, 'min' => 1, 'max' => 10, @@ -1294,7 +1294,7 @@ trait DatabaseBase 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'attributeId' => 'floatRange', + 'key' => 'floatRange', 'required' => false, 'min' => 1.1, 'max' => 1.4, @@ -1305,7 +1305,7 @@ trait DatabaseBase 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'attributeId' => 'probability', + 'key' => 'probability', 'required' => false, 'min' => 0, 'max' => 1, @@ -1316,7 +1316,7 @@ trait DatabaseBase 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'attributeId' => 'upperBound', + 'key' => 'upperBound', 'required' => false, 'max' => 10, ]); @@ -1326,7 +1326,7 @@ trait DatabaseBase 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'attributeId' => 'lowerBound', + 'key' => 'lowerBound', 'required' => false, 'min' => 5, ]); @@ -1339,7 +1339,7 @@ trait DatabaseBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'attributeId' => 'invalidRange', + 'key' => 'invalidRange', 'required' => false, 'min' => 4, 'max' => 3, @@ -1349,7 +1349,7 @@ trait DatabaseBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'attributeId' => 'defaultArray', + 'key' => 'defaultArray', 'required' => false, 'default' => 42, 'array' => true, @@ -1788,7 +1788,7 @@ trait DatabaseBase 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'attributeId' => 'attribute', + 'key' => 'attribute', 'size' => 64, 'required' => true, ]); @@ -1804,7 +1804,7 @@ trait DatabaseBase 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'indexId' => 'key_attribute', + 'key' => 'key_attribute', 'type' => 'key', 'attributes' => [$attribute['body']['key']], ]); @@ -1829,13 +1829,41 @@ trait DatabaseBase $this->assertEquals(201, $document1['headers']['status-code']); + $document2 = $this->client->call(Client::METHOD_POST, '/database/collections/' . $collectionId . '/documents', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'documentId' => 'unique()', + 'data' => [ + 'attribute' => 'one', + ], + 'read' => [], + 'write' => [$user], + ]); + + $this->assertEquals(201, $document2['headers']['status-code']); + + $document3 = $this->client->call(Client::METHOD_POST, '/database/collections/' . $collectionId . '/documents', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'documentId' => 'unique()', + 'data' => [ + 'attribute' => 'one', + ], + 'read' => [], + 'write' => [], + ]); + + $this->assertEquals(201, $document3['headers']['status-code']); + $documents = $this->client->call(Client::METHOD_GET, '/database/collections/' . $collectionId . '/documents', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); - $this->assertEquals(1, $documents['body']['sum']); - $this->assertCount(1, $documents['body']['documents']); + $this->assertEquals(3, $documents['body']['sum']); + $this->assertCount(3, $documents['body']['documents']); /* * Test for Failure @@ -1894,7 +1922,7 @@ trait DatabaseBase 'x-appwrite-project' => $this->getProject()['$id'], ])); - $this->assertEquals(404, $documents['headers']['status-code']); + $this->assertEquals(401, $documents['headers']['status-code']); } /** @@ -1907,7 +1935,7 @@ trait DatabaseBase 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'indexId' => 'unique_title', + 'key' => 'unique_title', 'type' => 'unique', 'attributes' => ['title'], ]); diff --git a/tests/e2e/Services/Database/DatabaseCustomServerTest.php b/tests/e2e/Services/Database/DatabaseCustomServerTest.php index 9f8c7130e4..659839debe 100644 --- a/tests/e2e/Services/Database/DatabaseCustomServerTest.php +++ b/tests/e2e/Services/Database/DatabaseCustomServerTest.php @@ -165,7 +165,7 @@ class DatabaseCustomServerTest extends Scope 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'attributeId' => 'firstName', + 'key' => 'firstName', 'size' => 256, 'required' => true, ]); @@ -175,7 +175,7 @@ class DatabaseCustomServerTest extends Scope 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'attributeId' => 'lastName', + 'key' => 'lastName', 'size' => 256, 'required' => true, ]); @@ -185,7 +185,7 @@ class DatabaseCustomServerTest extends Scope 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'attributeId' => 'unneeded', + 'key' => 'unneeded', 'size' => 256, 'required' => true, ]); @@ -214,7 +214,7 @@ class DatabaseCustomServerTest extends Scope 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'indexId' => 'key_lastName', + 'key' => 'key_lastName', 'type' => 'key', 'attributes' => [ 'lastName', @@ -275,7 +275,7 @@ class DatabaseCustomServerTest extends Scope return [ 'collectionId' => $actors['body']['$id'], - 'indexId' => $index['body']['key'], + 'key' => $index['body']['key'], ]; } @@ -284,7 +284,7 @@ class DatabaseCustomServerTest extends Scope */ public function testDeleteIndex($data): array { - $index = $this->client->call(Client::METHOD_DELETE, '/database/collections/' . $data['collectionId'] . '/indexes/'. $data['indexId'], array_merge([ + $index = $this->client->call(Client::METHOD_DELETE, '/database/collections/' . $data['collectionId'] . '/indexes/'. $data['key'], array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -316,7 +316,7 @@ class DatabaseCustomServerTest extends Scope 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'attributeId' => 'attribute1', + 'key' => 'attribute1', 'size' => 16, 'required' => true, ]); @@ -326,7 +326,7 @@ class DatabaseCustomServerTest extends Scope 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'attributeId' => 'attribute2', + 'key' => 'attribute2', 'size' => 16, 'required' => true, ]); @@ -343,7 +343,7 @@ class DatabaseCustomServerTest extends Scope 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'indexId' => 'index1', + 'key' => 'index1', 'type' => 'key', 'attributes' => ['attribute1', 'attribute2'], 'orders' => ['ASC', 'ASC'], @@ -354,7 +354,7 @@ class DatabaseCustomServerTest extends Scope 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'indexId' => 'index2', + 'key' => 'index2', 'type' => 'key', 'attributes' => ['attribute2'], ]); @@ -428,7 +428,7 @@ class DatabaseCustomServerTest extends Scope 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'attributeId' => 'attribute1', + 'key' => 'attribute1', 'size' => 16, 'required' => true, ]); @@ -438,7 +438,7 @@ class DatabaseCustomServerTest extends Scope 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'attributeId' => 'attribute2', + 'key' => 'attribute2', 'size' => 16, 'required' => true, ]); @@ -455,7 +455,7 @@ class DatabaseCustomServerTest extends Scope 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'indexId' => 'index1', + 'key' => 'index1', 'type' => 'key', 'attributes' => ['attribute1', 'attribute2'], 'orders' => ['ASC', 'ASC'], @@ -466,7 +466,7 @@ class DatabaseCustomServerTest extends Scope 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'indexId' => 'index2', + 'key' => 'index2', 'type' => 'key', 'attributes' => ['attribute2'], ]); @@ -615,7 +615,7 @@ class DatabaseCustomServerTest extends Scope // 'x-appwrite-project' => $this->getProject()['$id'], // 'x-appwrite-key' => $this->getProject()['apiKey'] // ]), [ - // 'attributeId' => "attribute{$i}", + // 'key' => "attribute{$i}", // 'required' => false, // ]); @@ -629,7 +629,7 @@ class DatabaseCustomServerTest extends Scope // 'x-appwrite-project' => $this->getProject()['$id'], // 'x-appwrite-key' => $this->getProject()['apiKey'] // ]), [ - // 'attributeId' => "tooMany", + // 'key' => "tooMany", // 'required' => false, // ]); @@ -663,7 +663,7 @@ class DatabaseCustomServerTest extends Scope 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'attributeId' => "attribute{$i}", + 'key' => "attribute{$i}", 'size' => 1024, 'required' => true, ]); @@ -678,7 +678,7 @@ class DatabaseCustomServerTest extends Scope 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'attributeId' => 'tooWide', + 'key' => 'tooWide', 'size' => 1024, 'required' => true, ]); @@ -714,7 +714,7 @@ class DatabaseCustomServerTest extends Scope 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'attributeId' => "attribute{$i}", + 'key' => "attribute{$i}", 'size' => 64, 'required' => true, ]); @@ -751,7 +751,7 @@ class DatabaseCustomServerTest extends Scope 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'indexId' => "key_attribute{$i}", + 'key' => "key_attribute{$i}", 'type' => 'key', 'attributes' => ["attribute{$i}"], ]); @@ -780,7 +780,7 @@ class DatabaseCustomServerTest extends Scope 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'indexId' => 'tooMany', + 'key' => 'tooMany', 'type' => 'key', 'attributes' => ['attribute61'], ]); diff --git a/tests/e2e/Services/Database/DatabasePermissionsGuestTest.php b/tests/e2e/Services/Database/DatabasePermissionsGuestTest.php index 1e6c6b7e18..f5e8ad350e 100644 --- a/tests/e2e/Services/Database/DatabasePermissionsGuestTest.php +++ b/tests/e2e/Services/Database/DatabasePermissionsGuestTest.php @@ -26,7 +26,7 @@ class DatabasePermissionsGuestTest extends Scope $collection = ['id' => $movies['body']['$id']]; $this->client->call(Client::METHOD_POST, '/database/collections/' . $collection['id'] . '/attributes/string', $this->getServerHeader(), [ - 'attributeId' => 'title', + 'key' => 'title', 'size' => 256, 'required' => true, ]); diff --git a/tests/e2e/Services/Database/DatabasePermissionsMemberTest.php b/tests/e2e/Services/Database/DatabasePermissionsMemberTest.php index e28fb559ac..84c5ee6d6b 100644 --- a/tests/e2e/Services/Database/DatabasePermissionsMemberTest.php +++ b/tests/e2e/Services/Database/DatabasePermissionsMemberTest.php @@ -65,7 +65,7 @@ class DatabasePermissionsMemberTest extends Scope $this->collections = ['public' => $public['body']['$id']]; $response = $this->client->call(Client::METHOD_POST, '/database/collections/' . $this->collections['public'] . '/attributes/string', $this->getServerHeader(), [ - 'attributeId' => 'title', + 'key' => 'title', 'size' => 256, 'required' => true, ]); @@ -83,7 +83,7 @@ class DatabasePermissionsMemberTest extends Scope $this->collections['private'] = $private['body']['$id']; $this->client->call(Client::METHOD_POST, '/database/collections/' . $this->collections['private'] . '/attributes/string', $this->getServerHeader(), [ - 'attributeId' => 'title', + 'key' => 'title', 'size' => 256, 'required' => true, ]); diff --git a/tests/e2e/Services/Database/DatabasePermissionsTeamTest.php b/tests/e2e/Services/Database/DatabasePermissionsTeamTest.php index 218c9cd78a..75b4d52d0b 100644 --- a/tests/e2e/Services/Database/DatabasePermissionsTeamTest.php +++ b/tests/e2e/Services/Database/DatabasePermissionsTeamTest.php @@ -45,7 +45,7 @@ class DatabasePermissionsTeamTest extends Scope $this->collections['collection1'] = $collection1['body']['$id']; $this->client->call(Client::METHOD_POST, '/database/collections/' . $this->collections['collection1'] . '/attributes/string', $this->getServerHeader(), [ - 'attributeId' => 'title', + 'key' => 'title', 'size' => 256, 'required' => true, ]); @@ -61,7 +61,7 @@ class DatabasePermissionsTeamTest extends Scope $this->collections['collection2'] = $collection2['body']['$id']; $this->client->call(Client::METHOD_POST, '/database/collections/' . $this->collections['collection2'] . '/attributes/string', $this->getServerHeader(), [ - 'attributeId' => 'title', + 'key' => 'title', 'size' => 256, 'required' => true, ]); @@ -160,7 +160,7 @@ class DatabasePermissionsTeamTest extends Scope if ($success) { $this->assertCount(1, $documents['body']['documents']); } else { - $this->assertEquals(404, $documents['headers']['status-code']); + $this->assertEquals(401, $documents['headers']['status-code']); } } diff --git a/tests/e2e/Services/Realtime/RealtimeConsoleClientTest.php b/tests/e2e/Services/Realtime/RealtimeConsoleClientTest.php index 00c39c5d05..0b19665bf8 100644 --- a/tests/e2e/Services/Realtime/RealtimeConsoleClientTest.php +++ b/tests/e2e/Services/Realtime/RealtimeConsoleClientTest.php @@ -61,7 +61,7 @@ class RealtimeConsoleClientTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'attributeId' => 'name', + 'key' => 'name', 'size' => 256, 'required' => true, ]); @@ -134,7 +134,7 @@ class RealtimeConsoleClientTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'indexId' => 'key_name', + 'key' => 'key_name', 'type' => 'key', 'attributes' => [ 'name', diff --git a/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php b/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php index ed10aefa59..6b815c9043 100644 --- a/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php +++ b/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php @@ -77,9 +77,7 @@ class RealtimeCustomClientTest extends Scope 'files', 'files.1', 'collections', - 'collections.1', 'collections.1.documents', - 'collections.2', 'collections.2.documents', 'documents', 'documents.1', @@ -93,15 +91,13 @@ class RealtimeCustomClientTest extends Scope $this->assertEquals('connected', $response['type']); $this->assertNotEmpty($response['data']); $this->assertNotEmpty($response['data']['user']); - $this->assertCount(12, $response['data']['channels']); + $this->assertCount(10, $response['data']['channels']); $this->assertContains('account', $response['data']['channels']); $this->assertContains('account.' . $userId, $response['data']['channels']); $this->assertContains('files', $response['data']['channels']); $this->assertContains('files.1', $response['data']['channels']); $this->assertContains('collections', $response['data']['channels']); - $this->assertContains('collections.1', $response['data']['channels']); $this->assertContains('collections.1.documents', $response['data']['channels']); - $this->assertContains('collections.2', $response['data']['channels']); $this->assertContains('collections.2.documents', $response['data']['channels']); $this->assertContains('documents', $response['data']['channels']); $this->assertContains('documents.1', $response['data']['channels']); @@ -561,24 +557,11 @@ class RealtimeCustomClientTest extends Scope ]), [ 'collectionId' => 'unique()', 'name' => 'Actors', - 'read' => ['role:all'], - 'write' => ['role:all'], - 'permission' => 'collection' + 'read' => [], + 'write' => [], + 'permission' => 'document' ]); - $response = json_decode($client->receive(), true); - - $this->assertArrayHasKey('type', $response); - $this->assertArrayHasKey('data', $response); - $this->assertEquals('event', $response['type']); - $this->assertNotEmpty($response['data']); - $this->assertArrayHasKey('timestamp', $response['data']); - $this->assertCount(2, $response['data']['channels']); - $this->assertContains('collections', $response['data']['channels']); - $this->assertContains('collections.' . $actors['body']['$id'], $response['data']['channels']); - $this->assertEquals('database.collections.create', $response['data']['event']); - $this->assertNotEmpty($response['data']['payload']); - $data = ['actorsId' => $actors['body']['$id']]; $name = $this->client->call(Client::METHOD_POST, '/database/collections/' . $data['actorsId'] . '/attributes/string', array_merge([ @@ -662,7 +645,6 @@ class RealtimeCustomClientTest extends Scope $this->assertEquals($response['data']['payload']['name'], 'Chris Evans 2'); - /** * Test Document Delete */ @@ -703,6 +685,166 @@ class RealtimeCustomClientTest extends Scope $client->close(); } + public function testChannelDatabaseCollectionPermissions() + { + $user = $this->getUser(); + $session = $user['session'] ?? ''; + $projectId = $this->getProject()['$id']; + + $client = $this->getWebsocket(['documents', 'collections'], [ + 'origin' => 'http://localhost', + 'cookie' => 'a_session_'.$projectId.'=' . $session + ]); + + $response = json_decode($client->receive(), true); + + $this->assertArrayHasKey('type', $response); + $this->assertArrayHasKey('data', $response); + $this->assertEquals('connected', $response['type']); + $this->assertNotEmpty($response['data']); + $this->assertCount(2, $response['data']['channels']); + $this->assertContains('documents', $response['data']['channels']); + $this->assertContains('collections', $response['data']['channels']); + $this->assertNotEmpty($response['data']['user']); + $this->assertEquals($user['$id'], $response['data']['user']['$id']); + + /** + * Test Collection Create + */ + $actors = $this->client->call(Client::METHOD_POST, '/database/collections', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => 'unique()', + 'name' => 'Actors', + 'read' => ['role:all'], + 'write' => ['role:all'], + 'permission' => 'collection' + ]); + + $data = ['actorsId' => $actors['body']['$id']]; + + $name = $this->client->call(Client::METHOD_POST, '/database/collections/' . $data['actorsId'] . '/attributes/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'name', + 'size' => 256, + 'required' => true, + ]); + + $this->assertEquals($name['headers']['status-code'], 201); + $this->assertEquals($name['body']['key'], 'name'); + $this->assertEquals($name['body']['type'], 'string'); + $this->assertEquals($name['body']['size'], 256); + $this->assertEquals($name['body']['required'], true); + + sleep(2); + + /** + * Test Document Create + */ + $document = $this->client->call(Client::METHOD_POST, '/database/collections/' . $data['actorsId'] . '/documents', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'documentId' => 'unique()', + 'data' => [ + 'name' => 'Chris Evans' + ], + 'read' => [], + 'write' => [], + ]); + + $response = json_decode($client->receive(), true); + + $this->assertArrayHasKey('type', $response); + $this->assertArrayHasKey('data', $response); + $this->assertEquals('event', $response['type']); + $this->assertNotEmpty($response['data']); + $this->assertArrayHasKey('timestamp', $response['data']); + $this->assertCount(3, $response['data']['channels']); + $this->assertContains('documents', $response['data']['channels']); + $this->assertContains('documents.' . $document['body']['$id'], $response['data']['channels']); + $this->assertContains('collections.' . $actors['body']['$id'] . '.documents', $response['data']['channels']); + $this->assertEquals('database.documents.create', $response['data']['event']); + $this->assertNotEmpty($response['data']['payload']); + $this->assertEquals($response['data']['payload']['name'], 'Chris Evans'); + + $data['documentId'] = $document['body']['$id']; + + /** + * Test Document Update + */ + $document = $this->client->call(Client::METHOD_PATCH, '/database/collections/' . $data['actorsId'] . '/documents/' . $data['documentId'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'data' => [ + 'name' => 'Chris Evans 2' + ], + 'read' => [], + 'write' => [], + ]); + + $response = json_decode($client->receive(), true); + + $this->assertArrayHasKey('type', $response); + $this->assertArrayHasKey('data', $response); + $this->assertEquals('event', $response['type']); + $this->assertNotEmpty($response['data']); + $this->assertArrayHasKey('timestamp', $response['data']); + $this->assertCount(3, $response['data']['channels']); + $this->assertContains('documents', $response['data']['channels']); + $this->assertContains('documents.' . $data['documentId'], $response['data']['channels']); + $this->assertContains('collections.' . $data['actorsId'] . '.documents', $response['data']['channels']); + $this->assertEquals('database.documents.update', $response['data']['event']); + $this->assertNotEmpty($response['data']['payload']); + + $this->assertEquals($response['data']['payload']['name'], 'Chris Evans 2'); + + /** + * Test Document Delete + */ + $document = $this->client->call(Client::METHOD_POST, '/database/collections/' . $data['actorsId'] . '/documents', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'documentId' => 'unique()', + 'data' => [ + 'name' => 'Bradley Cooper' + ], + 'read' => [], + 'write' => [], + ]); + + $client->receive(); + + $this->client->call(Client::METHOD_DELETE, '/database/collections/' . $data['actorsId'] . '/documents/' . $document['body']['$id'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $response = json_decode($client->receive(), true); + + $this->assertArrayHasKey('type', $response); + $this->assertArrayHasKey('data', $response); + $this->assertEquals('event', $response['type']); + $this->assertNotEmpty($response['data']); + $this->assertArrayHasKey('timestamp', $response['data']); + $this->assertCount(3, $response['data']['channels']); + $this->assertContains('documents', $response['data']['channels']); + $this->assertContains('documents.' . $document['body']['$id'], $response['data']['channels']); + $this->assertContains('collections.' . $data['actorsId'] . '.documents', $response['data']['channels']); + $this->assertEquals('database.documents.delete', $response['data']['event']); + $this->assertNotEmpty($response['data']['payload']); + $this->assertEquals($response['data']['payload']['name'], 'Bradley Cooper'); + + $client->close(); + } + public function testChannelFiles() { $user = $this->getUser(); diff --git a/tests/e2e/Services/Webhooks/WebhooksBase.php b/tests/e2e/Services/Webhooks/WebhooksBase.php index 304cc215f6..53e064d13d 100644 --- a/tests/e2e/Services/Webhooks/WebhooksBase.php +++ b/tests/e2e/Services/Webhooks/WebhooksBase.php @@ -57,7 +57,7 @@ trait WebhooksBase 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'attributeId' => 'firstName', + 'key' => 'firstName', 'size' => 256, 'required' => true, ]); @@ -67,7 +67,7 @@ trait WebhooksBase 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'attributeId' => 'lastName', + 'key' => 'lastName', 'size' => 256, 'required' => true, ]); @@ -77,7 +77,7 @@ trait WebhooksBase 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'attributeId' => 'extra', + 'key' => 'extra', 'size' => 64, 'required' => false, ]); diff --git a/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php b/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php index 09aaa63524..cb243c8c2b 100644 --- a/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php +++ b/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php @@ -64,7 +64,7 @@ class WebhooksCustomServerTest extends Scope 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'indexId' => 'fullname', + 'key' => 'fullname', 'type' => 'key', 'attributes' => ['lastName', 'firstName'], 'orders' => ['ASC', 'ASC'], @@ -125,7 +125,7 @@ class WebhooksCustomServerTest extends Scope 'write' => ['role:all'], 'permission' => 'document' ]); - + $this->assertEquals($actors['headers']['status-code'], 201); $this->assertNotEmpty($actors['body']['$id']); @@ -134,7 +134,7 @@ class WebhooksCustomServerTest extends Scope 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), []); - + $this->assertEquals($actors['headers']['status-code'], 204); $webhook = $this->getLastRequest(); diff --git a/tests/unit/Messaging/MessagingTest.php b/tests/unit/Messaging/MessagingTest.php index dd258d3ae6..a0cad0bae9 100644 --- a/tests/unit/Messaging/MessagingTest.php +++ b/tests/unit/Messaging/MessagingTest.php @@ -2,7 +2,7 @@ namespace Appwrite\Tests; -use Appwrite\Database\Document; +use Utopia\Database\Document; use Appwrite\Messaging\Adapter\Realtime; use PHPUnit\Framework\TestCase; @@ -195,4 +195,51 @@ class MessagingTest extends TestCase $this->assertArrayHasKey('account', $channels); $this->assertArrayNotHasKey('account.456', $channels); } + + public function testFromPayloadCollectionLevelPermissions(): void + { + /** + * Test Collection Level Permissions + */ + $result = Realtime::fromPayload( + event: 'database.documents.create', + payload: new Document([ + '$id' => 'test', + '$collection' => 'collection', + '$read' => ['role:admin'], + '$write' => ['role:admin'] + ]), + collection: new Document([ + '$id' => 'collection', + '$read' => ['role:all'], + '$write' => ['role:all'], + 'permission' => 'collection' + ]) + ); + + $this->assertContains('role:all', $result['roles']); + $this->assertNotContains('role:admin', $result['roles']); + + /** + * Test Document Level Permissions + */ + $result = Realtime::fromPayload( + event: 'database.documents.create', + payload: new Document([ + '$id' => 'test', + '$collection' => 'collection', + '$read' => ['role:all'], + '$write' => ['role:all'] + ]), + collection: new Document([ + '$id' => 'collection', + '$read' => ['role:admin'], + '$write' => ['role:admin'], + 'permission' => 'document' + ]) + ); + + $this->assertContains('role:all', $result['roles']); + $this->assertNotContains('role:admin', $result['roles']); + } }