Merge remote-tracking branch 'origin/0.16.x' into datetime-attributes-merge

# Conflicts:
#	app/controllers/api/account.php
#	app/controllers/api/databases.php
#	app/controllers/api/functions.php
#	app/controllers/api/projects.php
#	app/controllers/api/storage.php
#	app/controllers/api/teams.php
#	app/controllers/api/users.php
#	app/controllers/general.php
#	app/controllers/shared/api.php
This commit is contained in:
Jake Barnby
2022-08-16 19:20:26 +12:00
79 changed files with 952 additions and 822 deletions
+106 -101
View File
@@ -69,28 +69,28 @@ function createAttribute(string $databaseId, string $collectionId, Document $att
$db = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($db->isEmpty()) {
throw new Exception('Database not found', 404, Exception::DATABASE_NOT_FOUND);
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
$collection = $dbForProject->getDocument('database_' . $db->getInternalId(), $collectionId);
if ($collection->isEmpty()) {
throw new Exception('Collection not found', 404, Exception::COLLECTION_NOT_FOUND);
throw new Exception(Exception::COLLECTION_NOT_FOUND);
}
if (!empty($format)) {
if (!Structure::hasFormat($format, $type)) {
throw new Exception("Format {$format} not available for {$type} attributes.", 400, Exception::ATTRIBUTE_FORMAT_UNSUPPORTED);
throw new Exception(Exception::ATTRIBUTE_FORMAT_UNSUPPORTED, "Format {$format} not available for {$type} attributes.");
}
}
// Must throw here since dbForProject->createAttribute is performed by db worker
if ($required && $default) {
throw new Exception('Cannot set default value for required attribute', 400, Exception::ATTRIBUTE_DEFAULT_UNSUPPORTED);
throw new Exception(Exception::ATTRIBUTE_DEFAULT_UNSUPPORTED, 'Cannot set default value for required attribute');
}
if ($array && $default) {
throw new Exception('Cannot set default value for array attributes', 400, Exception::ATTRIBUTE_DEFAULT_UNSUPPORTED);
throw new Exception(Exception::ATTRIBUTE_DEFAULT_UNSUPPORTED, 'Cannot set default value for array attributes');
}
try {
@@ -116,9 +116,9 @@ function createAttribute(string $databaseId, string $collectionId, Document $att
$dbForProject->checkAttribute($collection, $attribute);
$attribute = $dbForProject->createDocument('attributes', $attribute);
} catch (DuplicateException $exception) {
throw new Exception('Attribute already exists', 409, Exception::ATTRIBUTE_ALREADY_EXISTS);
throw new Exception(Exception::ATTRIBUTE_ALREADY_EXISTS);
} catch (LimitException $exception) {
throw new Exception('Attribute limit exceeded', 400, Exception::ATTRIBUTE_LIMIT_EXCEEDED);
throw new Exception(Exception::ATTRIBUTE_LIMIT_EXCEEDED, 'Attribute limit exceeded');
}
$dbForProject->deleteCachedDocument('database_' . $db->getInternalId(), $collectionId);
@@ -186,7 +186,7 @@ App::post('/v1/databases')
$collections = Config::getParam('collections', [])['collections'] ?? [];
if (empty($collections)) {
throw new Exception('Collections collection is not configured.', 500, Exception::GENERAL_SERVER_ERROR);
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'The "collections" collection is not configured.');
}
$attributes = [];
@@ -217,7 +217,7 @@ App::post('/v1/databases')
}
$dbForProject->createCollection('database_' . $database->getInternalId(), $attributes, $indexes);
} catch (DuplicateException $th) {
throw new Exception('Database already exists', 409, Exception::DATABASE_ALREADY_EXISTS);
throw new Exception(Exception::DATABASE_ALREADY_EXISTS);
}
$audits
@@ -268,7 +268,7 @@ App::get('/v1/databases')
$cursorDocument = $dbForProject->getDocument('databases', $cursor);
if ($cursorDocument->isEmpty()) {
throw new Exception("Collection '{$cursor}' for the 'cursor' value not found.", 400, Exception::GENERAL_CURSOR_NOT_FOUND);
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Collection '{$cursor}' for the 'cursor' value not found.");
}
$queries[] = $cursorDirection === Database::CURSOR_AFTER ? Query::cursorAfter($cursorDocument) : Query::cursorBefore($cursorDocument);
@@ -302,7 +302,7 @@ App::get('/v1/databases/:databaseId')
$database = $dbForProject->getDocument('databases', $databaseId);
if ($database->isEmpty()) {
throw new Exception('Database not found', 404, Exception::DATABASE_NOT_FOUND);
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
$usage->setParam('databases.read', 1);
@@ -333,7 +333,7 @@ App::get('/v1/databases/:databaseId/logs')
$database = $dbForProject->getDocument('databases', $databaseId);
if ($database->isEmpty()) {
throw new Exception('Database not found', 404, Exception::DATABASE_NOT_FOUND);
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
$audit = new Audit($dbForProject);
@@ -413,7 +413,7 @@ App::put('/v1/databases/:databaseId')
$database = $dbForProject->getDocument('databases', $databaseId);
if ($database->isEmpty()) {
throw new Exception('Database not found', 404, Exception::DATABASE_NOT_FOUND);
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
try {
@@ -421,9 +421,9 @@ App::put('/v1/databases/:databaseId')
->setAttribute('name', $name)
->setAttribute('search', implode(' ', [$databaseId, $name])));
} catch (AuthorizationException $exception) {
throw new Exception('Unauthorized permissions', 401, Exception::USER_UNAUTHORIZED);
throw new Exception(Exception::USER_UNAUTHORIZED);
} catch (StructureException $exception) {
throw new Exception('Bad structure. ' . $exception->getMessage(), 400, Exception::DOCUMENT_INVALID_STRUCTURE);
throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, 'Bad structure. ' . $exception->getMessage());
}
$audits
@@ -460,11 +460,11 @@ App::delete('/v1/databases/:databaseId')
$database = $dbForProject->getDocument('databases', $databaseId);
if ($database->isEmpty()) {
throw new Exception('Database not found', 404, Exception::DATABASE_NOT_FOUND);
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
if (!$dbForProject->deleteDocument('databases', $databaseId)) {
throw new Exception('Failed to remove collection from DB', 500, Exception::GENERAL_SERVER_ERROR);
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove collection from DB');
}
$dbForProject->deleteCachedCollection('databases' . $database->getInternalId());
@@ -518,7 +518,7 @@ App::post('/v1/databases/:databaseId/collections')
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($database->isEmpty()) {
throw new Exception('Database not found', 404, Exception::DATABASE_NOT_FOUND);
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
$collectionId = $collectionId == 'unique()' ? $dbForProject->getId() : $collectionId;
@@ -539,9 +539,9 @@ App::post('/v1/databases/:databaseId/collections')
$dbForProject->createCollection('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId());
} catch (DuplicateException $th) {
throw new Exception('Collection already exists', 409, Exception::COLLECTION_ALREADY_EXISTS);
throw new Exception(Exception::COLLECTION_ALREADY_EXISTS);
} catch (LimitException $th) {
throw new Exception('Collection limit exceeded', 400, Exception::COLLECTION_LIMIT_EXCEEDED);
throw new Exception(Exception::COLLECTION_LIMIT_EXCEEDED);
}
$audits
@@ -589,7 +589,7 @@ App::get('/v1/databases/:databaseId/collections')
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($database->isEmpty()) {
throw new Exception('Database not found', 404, Exception::DATABASE_NOT_FOUND);
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
$filterQueries = [];
@@ -606,7 +606,7 @@ App::get('/v1/databases/:databaseId/collections')
$cursorDocument = $dbForProject->getDocument('database_' . $database->getInternalId(), $cursor);
if ($cursorDocument->isEmpty()) {
throw new Exception("Collection '{$cursor}' for the 'cursor' value not found.", 400, Exception::GENERAL_CURSOR_NOT_FOUND);
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Collection '{$cursor}' for the 'cursor' value not found.");
}
$queries[] = $cursorDirection === Database::CURSOR_AFTER ? Query::cursorAfter($cursorDocument) : Query::cursorBefore($cursorDocument);
@@ -644,12 +644,12 @@ App::get('/v1/databases/:databaseId/collections/:collectionId')
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($database->isEmpty()) {
throw new Exception('Database not found', 404, Exception::DATABASE_NOT_FOUND);
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
$collection = $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId);
if ($collection->isEmpty()) {
throw new Exception('Collection not found', 404, Exception::COLLECTION_NOT_FOUND);
throw new Exception(Exception::COLLECTION_NOT_FOUND);
}
$usage
@@ -684,13 +684,13 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/logs')
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($database->isEmpty()) {
throw new Exception('Database not found', 404, Exception::DATABASE_NOT_FOUND);
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
$collectionDocument = $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId);
$collection = $dbForProject->getCollection('database_' . $database->getInternalId() . '_collection_' . $collectionDocument->getInternalId());
if ($collection->isEmpty()) {
throw new Exception('Collection not found', 404, Exception::COLLECTION_NOT_FOUND);
throw new Exception(Exception::COLLECTION_NOT_FOUND);
}
$audit = new Audit($dbForProject);
@@ -779,12 +779,12 @@ App::put('/v1/databases/:databaseId/collections/:collectionId')
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($database->isEmpty()) {
throw new Exception('Database not found', 404, Exception::DATABASE_NOT_FOUND);
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
$collection = $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId);
if ($collection->isEmpty()) {
throw new Exception('Collection not found', 404, Exception::COLLECTION_NOT_FOUND);
throw new Exception(Exception::COLLECTION_NOT_FOUND);
}
$read ??= $collection->getRead() ?? []; // By default inherit read permissions
@@ -800,9 +800,9 @@ App::put('/v1/databases/:databaseId/collections/:collectionId')
->setAttribute('enabled', $enabled)
->setAttribute('search', implode(' ', [$collectionId, $name])));
} catch (AuthorizationException $exception) {
throw new Exception('Unauthorized permissions', 401, Exception::USER_UNAUTHORIZED);
throw new Exception(Exception::USER_UNAUTHORIZED);
} catch (StructureException $exception) {
throw new Exception('Bad structure. ' . $exception->getMessage(), 400, Exception::DOCUMENT_INVALID_STRUCTURE);
throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, 'Bad structure. ' . $exception->getMessage());
}
$audits
@@ -847,17 +847,17 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId')
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($database->isEmpty()) {
throw new Exception('Database not found', 404, Exception::DATABASE_NOT_FOUND);
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
$collection = $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId);
if ($collection->isEmpty()) {
throw new Exception('Collection not found', 404, Exception::COLLECTION_NOT_FOUND);
throw new Exception(Exception::COLLECTION_NOT_FOUND);
}
if (!$dbForProject->deleteDocument('database_' . $database->getInternalId(), $collectionId)) {
throw new Exception('Failed to remove collection from DB', 500, Exception::GENERAL_SERVER_ERROR);
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove collection from DB');
}
$dbForProject->deleteCachedCollection('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId());
@@ -917,7 +917,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/string
// Ensure attribute default is within required size
$validator = new Text($size);
if (!is_null($default) && !$validator->isValid($default)) {
throw new Exception($validator->getDescription(), 400, Exception::ATTRIBUTE_VALUE_INVALID);
throw new Exception(Exception::ATTRIBUTE_VALUE_INVALID, $validator->getDescription());
}
$attribute = createAttribute($databaseId, $collectionId, new Document([
@@ -1007,13 +1007,13 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/enum')
foreach ($elements as $element) {
$length = \strlen($element);
if ($length === 0) {
throw new Exception('Each enum element must not be empty', 400, Exception::ATTRIBUTE_VALUE_INVALID);
throw new Exception(Exception::ATTRIBUTE_VALUE_INVALID, 'Each enum element must not be empty');
}
$size = ($length > $size) ? $length : $size;
}
if (!is_null($default) && !in_array($default, $elements)) {
throw new Exception('Default value not found in elements', 400, Exception::ATTRIBUTE_VALUE_INVALID);
throw new Exception(Exception::ATTRIBUTE_VALUE_INVALID, 'Default value not found in elements');
}
$attribute = createAttribute($databaseId, $collectionId, new Document([
@@ -1147,13 +1147,13 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/intege
$max = (is_null($max)) ? PHP_INT_MAX : \intval($max);
if ($min > $max) {
throw new Exception('Minimum value must be lesser than maximum value', 400, Exception::ATTRIBUTE_VALUE_INVALID);
throw new Exception(Exception::ATTRIBUTE_VALUE_INVALID, 'Minimum value must be lesser than maximum value');
}
$validator = new Range($min, $max, Database::VAR_INTEGER);
if (!is_null($default) && !$validator->isValid($default)) {
throw new Exception($validator->getDescription(), 400, Exception::ATTRIBUTE_VALUE_INVALID);
throw new Exception(Exception::ATTRIBUTE_VALUE_INVALID, $validator->getDescription());
}
$size = $max > 2147483647 ? 8 : 4; // Automatically create BigInt depending on max value
@@ -1217,7 +1217,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/float'
$max = (is_null($max)) ? PHP_FLOAT_MAX : \floatval($max);
if ($min > $max) {
throw new Exception('Minimum value must be lesser than maximum value', 400, Exception::ATTRIBUTE_VALUE_INVALID);
throw new Exception(Exception::ATTRIBUTE_VALUE_INVALID, 'Minimum value must be lesser than maximum value');
}
// Ensure default value is a float
@@ -1228,7 +1228,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/float'
$validator = new Range($min, $max, Database::VAR_FLOAT);
if (!is_null($default) && !$validator->isValid($default)) {
throw new Exception($validator->getDescription(), 400, Exception::ATTRIBUTE_VALUE_INVALID);
throw new Exception(Exception::ATTRIBUTE_VALUE_INVALID, $validator->getDescription());
}
$attribute = createAttribute($databaseId, $collectionId, new Document([
@@ -1361,12 +1361,12 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/attributes')
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($database->isEmpty()) {
throw new Exception('Database not found', 404, Exception::DATABASE_NOT_FOUND);
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
$collection = $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId);
if ($collection->isEmpty()) {
throw new Exception('Collection not found', 404, Exception::COLLECTION_NOT_FOUND);
throw new Exception(Exception::COLLECTION_NOT_FOUND);
}
$attributes = $collection->getAttribute('attributes');
@@ -1413,19 +1413,19 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/attributes/:key')
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($database->isEmpty()) {
throw new Exception('Database not found', 404, Exception::DATABASE_NOT_FOUND);
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
$collection = $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId);
if ($collection->isEmpty()) {
throw new Exception('Collection not found', 404, Exception::COLLECTION_NOT_FOUND);
throw new Exception(Exception::COLLECTION_NOT_FOUND);
}
$attribute = $dbForProject->getDocument('attributes', $database->getInternalId() . '_' . $collection->getInternalId() . '_' . $key);
if ($attribute->isEmpty()) {
throw new Exception('Attribute not found', 404, Exception::ATTRIBUTE_NOT_FOUND);
throw new Exception(Exception::ATTRIBUTE_NOT_FOUND);
}
// Select response model based on type and format
@@ -1480,18 +1480,18 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/attributes/:key
$db = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($db->isEmpty()) {
throw new Exception('Database not found', 404, Exception::DATABASE_NOT_FOUND);
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
$collection = $dbForProject->getDocument('database_' . $db->getInternalId(), $collectionId);
if ($collection->isEmpty()) {
throw new Exception('Collection not found', 404, Exception::COLLECTION_NOT_FOUND);
throw new Exception(Exception::COLLECTION_NOT_FOUND);
}
$attribute = $dbForProject->getDocument('attributes', $db->getInternalId() . '_' . $collection->getInternalId() . '_' . $key);
if ($attribute->isEmpty()) {
throw new Exception('Attribute not found', 404, Exception::ATTRIBUTE_NOT_FOUND);
throw new Exception(Exception::ATTRIBUTE_NOT_FOUND);
}
// Only update status if removing available attribute
@@ -1579,12 +1579,12 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/indexes')
$db = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($db->isEmpty()) {
throw new Exception('Database not found', 404, Exception::DATABASE_NOT_FOUND);
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
$collection = $dbForProject->getDocument('database_' . $db->getInternalId(), $collectionId);
if ($collection->isEmpty()) {
throw new Exception('Collection not found', 404, Exception::COLLECTION_NOT_FOUND);
throw new Exception(Exception::COLLECTION_NOT_FOUND);
}
$count = $dbForProject->count('indexes', [
@@ -1595,7 +1595,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/indexes')
$limit = 64 - MariaDB::getNumberOfDefaultIndexes();
if ($count >= $limit) {
throw new Exception('Index limit exceeded', 400, Exception::INDEX_LIMIT_EXCEEDED);
throw new Exception(Exception::INDEX_LIMIT_EXCEEDED, 'Index limit exceeded');
}
// Convert Document[] to array of attribute metadata
@@ -1641,7 +1641,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/indexes')
$attributeIndex = \array_search($attribute, array_column($oldAttributes, 'key'));
if ($attributeIndex === false) {
throw new Exception('Unknown attribute: ' . $attribute, 400, Exception::ATTRIBUTE_UNKNOWN);
throw new Exception(Exception::ATTRIBUTE_UNKNOWN, 'Unknown attribute: ' . $attribute);
}
$attributeStatus = $oldAttributes[$attributeIndex]['status'];
@@ -1650,7 +1650,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/indexes')
// ensure attribute is available
if ($attributeStatus !== 'available') {
throw new Exception('Attribute not available: ' . $oldAttributes[$attributeIndex]['key'], 400, Exception::ATTRIBUTE_NOT_AVAILABLE);
throw new Exception(Exception::ATTRIBUTE_NOT_AVAILABLE, 'Attribute not available: ' . $oldAttributes[$attributeIndex]['key']);
}
// set attribute size as index length only for strings
@@ -1672,7 +1672,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/indexes')
'orders' => $orders,
]));
} catch (DuplicateException $th) {
throw new Exception('Index already exists', 409, Exception::INDEX_ALREADY_EXISTS);
throw new Exception(Exception::INDEX_ALREADY_EXISTS);
}
$dbForProject->deleteCachedDocument('database_' . $db->getInternalId(), $collectionId);
@@ -1727,12 +1727,12 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/indexes')
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($database->isEmpty()) {
throw new Exception('Database not found', 404, Exception::DATABASE_NOT_FOUND);
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
$collection = $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId);
if ($collection->isEmpty()) {
throw new Exception('Collection not found', 404, Exception::COLLECTION_NOT_FOUND);
throw new Exception(Exception::COLLECTION_NOT_FOUND);
}
$indexes = $collection->getAttribute('indexes');
@@ -1770,12 +1770,12 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/indexes/:key')
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($database->isEmpty()) {
throw new Exception('Database not found', 404, Exception::DATABASE_NOT_FOUND);
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
$collection = $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId);
if ($collection->isEmpty()) {
throw new Exception('Collection not found', 404, Exception::COLLECTION_NOT_FOUND);
throw new Exception(Exception::COLLECTION_NOT_FOUND);
}
$indexes = $collection->getAttribute('indexes');
@@ -1784,7 +1784,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/indexes/:key')
$indexIndex = array_search($key, array_column($indexes, 'key'));
if ($indexIndex === false) {
throw new Exception('Index not found', 404, Exception::INDEX_NOT_FOUND);
throw new Exception(Exception::INDEX_NOT_FOUND);
}
$index = new Document([\array_merge($indexes[$indexIndex], [
@@ -1824,18 +1824,18 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/indexes/:key')
$db = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($db->isEmpty()) {
throw new Exception('Database not found', 404, Exception::DATABASE_NOT_FOUND);
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
$collection = $dbForProject->getDocument('database_' . $db->getInternalId(), $collectionId);
if ($collection->isEmpty()) {
throw new Exception('Collection not found', 404, Exception::COLLECTION_NOT_FOUND);
throw new Exception(Exception::COLLECTION_NOT_FOUND);
}
$index = $dbForProject->getDocument('indexes', $db->getInternalId() . '_' . $collection->getInternalId() . '_' . $key);
if (empty($index->getId())) {
throw new Exception('Index not found', 404, Exception::INDEX_NOT_FOUND);
throw new Exception(Exception::INDEX_NOT_FOUND);
}
// Only update status if removing available index
@@ -1904,16 +1904,16 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents')
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($database->isEmpty()) {
throw new Exception('Database not found', 404, Exception::DATABASE_NOT_FOUND);
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
$data = (\is_string($data)) ? \json_decode($data, true) : $data; // Cast to JSON array
if (empty($data)) {
throw new Exception('Missing payload', 400, Exception::DOCUMENT_MISSING_PAYLOAD);
throw new Exception(Exception::DOCUMENT_MISSING_PAYLOAD);
}
if (isset($data['$id'])) {
throw new Exception('$id is not allowed for creating new documents, try update instead', 400, Exception::DOCUMENT_INVALID_STRUCTURE);
throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, '$id is not allowed for creating new documents, try update instead');
}
/**
@@ -1925,7 +1925,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents')
if ($collection->isEmpty() || !$collection->getAttribute('enabled')) {
if (!($mode === APP_MODE_ADMIN && Auth::isPrivilegedUser(Authorization::getRoles()))) {
throw new Exception('Collection not found', 404, Exception::COLLECTION_NOT_FOUND);
throw new Exception(Exception::COLLECTION_NOT_FOUND);
}
}
@@ -1933,7 +1933,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents')
if ($collection->getAttribute('permission') === 'collection') {
$validator = new Authorization('write');
if (!$validator->isValid($collection->getWrite())) {
throw new Exception('Unauthorized permissions', 401, Exception::USER_UNAUTHORIZED);
throw new Exception(Exception::USER_UNAUTHORIZED);
}
}
@@ -1948,13 +1948,12 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents')
if (!Auth::isAppUser($roles) && !Auth::isPrivilegedUser($roles)) {
foreach ($data['$read'] as $read) {
if (!Authorization::isRole($read)) {
// TODO: Isn't this a 401: Unauthorized Error ?
throw new Exception('Read permissions must be one of: (' . \implode(', ', $roles) . ')', 400, Exception::USER_UNAUTHORIZED);
throw new Exception(Exception::USER_UNAUTHORIZED, 'Read permissions must be one of: (' . \implode(', ', $roles) . ')');
}
}
foreach ($data['$write'] as $write) {
if (!Authorization::isRole($write)) {
throw new Exception('Write permissions must be one of: (' . \implode(', ', $roles) . ')', 400, Exception::USER_UNAUTHORIZED);
throw new Exception(Exception::USER_UNAUTHORIZED, 'Write permissions must be one of: (' . \implode(', ', $roles) . ')');
}
}
}
@@ -1968,9 +1967,9 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents')
}
$document->setAttribute('$collection', $collectionId);
} catch (StructureException $exception) {
throw new Exception($exception->getMessage(), 400, Exception::DOCUMENT_INVALID_STRUCTURE);
throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, $exception->getMessage());
} catch (DuplicateException $exception) {
throw new Exception('Document already exists', 409, Exception::DOCUMENT_ALREADY_EXISTS);
throw new Exception(Exception::DOCUMENT_ALREADY_EXISTS);
}
$events
@@ -2026,7 +2025,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents')
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($database->isEmpty()) {
throw new Exception('Database not found', 404, Exception::DATABASE_NOT_FOUND);
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
/**
* Skip Authorization to get the collection. Needed in case of empty permissions for document level permissions.
@@ -2037,7 +2036,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents')
if ($collection->isEmpty() || !$collection->getAttribute('enabled')) {
if (!($mode === APP_MODE_ADMIN && Auth::isPrivilegedUser(Authorization::getRoles()))) {
throw new Exception('Collection not found', 404, Exception::COLLECTION_NOT_FOUND);
throw new Exception(Exception::COLLECTION_NOT_FOUND);
}
}
@@ -2045,12 +2044,18 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents')
if ($collection->getAttribute('permission') === 'collection') {
$validator = new Authorization('read');
if (!$validator->isValid($collection->getRead())) {
throw new Exception('Unauthorized permissions', 401, Exception::USER_UNAUTHORIZED);
throw new Exception(Exception::USER_UNAUTHORIZED);
}
}
$filterQueries = \array_map(function ($query) {
return Query::parse($query);
$query = Query::parse($query);
if (\count($query->getValues()) > 100) {
throw new Exception(Exception::GENERAL_QUERY_LIMIT_EXCEEDED, "You cannot use more than 100 query values on attribute '{$query->getAttribute()}'");
}
return $query;
}, $queries);
$otherQueries = [];
@@ -2066,7 +2071,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents')
: $dbForProject->getDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $cursor);
if ($cursorDocument->isEmpty()) {
throw new Exception("Document '{$cursor}' for the 'cursor' value not found.", 400, Exception::GENERAL_CURSOR_NOT_FOUND);
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Document '{$cursor}' for the 'cursor' value not found.");
}
$otherQueries[] = $cursorDirection === Database::CURSOR_AFTER ? Query::cursorAfter($cursorDocument) : Query::cursorBefore($cursorDocument);
@@ -2078,7 +2083,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents')
$attributes = $collection->getAttribute('attributes', []);
$validator = new QueriesValidator(new QueryValidator($attributes), $attributes, $collection->getAttribute('indexes', []), true);
if (!$validator->isValid($allQueries)) {
throw new Exception($validator->getDescription(), 400, Exception::GENERAL_QUERY_INVALID);
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
}
}
@@ -2132,7 +2137,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($database->isEmpty()) {
throw new Exception('Database not found', 404, Exception::DATABASE_NOT_FOUND);
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
/**
* Skip Authorization to get the collection. Needed in case of empty permissions for document level permissions.
@@ -2141,7 +2146,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen
if ($collection->isEmpty() || !$collection->getAttribute('enabled')) {
if (!($mode === APP_MODE_ADMIN && Auth::isPrivilegedUser(Authorization::getRoles()))) {
throw new Exception('Collection not found', 404, Exception::COLLECTION_NOT_FOUND);
throw new Exception(Exception::COLLECTION_NOT_FOUND);
}
}
@@ -2149,7 +2154,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen
if ($collection->getAttribute('permission') === 'collection') {
$validator = new Authorization('read');
if (!$validator->isValid($collection->getRead())) {
throw new Exception('Unauthorized permissions', 401, Exception::USER_UNAUTHORIZED);
throw new Exception(Exception::USER_UNAUTHORIZED);
}
}
@@ -2166,7 +2171,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen
$document->setAttribute('$collection', $collectionId);
if ($document->isEmpty()) {
throw new Exception('No document found', 404, Exception::DOCUMENT_NOT_FOUND);
throw new Exception(Exception::DOCUMENT_NOT_FOUND);
}
$usage
@@ -2204,18 +2209,18 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($database->isEmpty()) {
throw new Exception('Database not found', 404, Exception::DATABASE_NOT_FOUND);
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
$collection = $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId);
if ($collection->isEmpty()) {
throw new Exception('Collection not found', 404, Exception::COLLECTION_NOT_FOUND);
throw new Exception(Exception::COLLECTION_NOT_FOUND);
}
$document = $dbForProject->getDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $documentId);
if ($document->isEmpty()) {
throw new Exception('No document found', 404, Exception::DOCUMENT_NOT_FOUND);
throw new Exception(Exception::DOCUMENT_NOT_FOUND);
}
$audit = new Audit($dbForProject);
@@ -2302,7 +2307,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($database->isEmpty()) {
throw new Exception('Database not found', 404, Exception::DATABASE_NOT_FOUND);
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
/**
* Skip Authorization to get the collection. Needed in case of empty permissions for document level permissions.
@@ -2311,7 +2316,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum
if ($collection->isEmpty() || !$collection->getAttribute('enabled')) {
if (!($mode === APP_MODE_ADMIN && Auth::isPrivilegedUser(Authorization::getRoles()))) {
throw new Exception('Collection not found', 404, Exception::COLLECTION_NOT_FOUND);
throw new Exception(Exception::COLLECTION_NOT_FOUND);
}
}
@@ -2319,7 +2324,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum
if ($collection->getAttribute('permission') === 'collection') {
$validator = new Authorization('write');
if (!$validator->isValid($collection->getWrite())) {
throw new Exception('Unauthorized permissions', 401, Exception::USER_UNAUTHORIZED);
throw new Exception(Exception::USER_UNAUTHORIZED);
}
$document = Authorization::skip(fn() => $dbForProject->getDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $documentId));
@@ -2329,17 +2334,17 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum
if ($document->isEmpty()) {
throw new Exception('Document not found', 404, Exception::DOCUMENT_NOT_FOUND);
throw new Exception(Exception::DOCUMENT_NOT_FOUND);
}
$data = (\is_string($data)) ? \json_decode($data, true) : $data; // Cast to JSON array
if (empty($data) && empty($read) && empty($write)) {
throw new Exception('Missing payload or read/write permissions', 400, Exception::DOCUMENT_MISSING_PAYLOAD);
throw new Exception(Exception::DOCUMENT_MISSING_PAYLOAD, 'Missing payload or read/write permissions');
}
if (!\is_array($data)) {
throw new Exception('Data param should be a valid JSON object', 400, Exception::DOCUMENT_INVALID_STRUCTURE);
throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, 'Data param should be a valid JSON object');
}
$data = \array_merge($document->getArrayCopy(), $data);
@@ -2357,14 +2362,14 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum
if (!is_null($read)) {
foreach ($data['$read'] as $read) {
if (!Authorization::isRole($read)) {
throw new Exception('Read permissions must be one of: (' . \implode(', ', $roles) . ')', 400, Exception::USER_UNAUTHORIZED);
throw new Exception(Exception::USER_UNAUTHORIZED, 'Read permissions must be one of: (' . \implode(', ', $roles) . ')');
}
}
}
if (!is_null($write)) {
foreach ($data['$write'] as $write) {
if (!Authorization::isRole($write)) {
throw new Exception('Write permissions must be one of: (' . \implode(', ', $roles) . ')', 400, Exception::USER_UNAUTHORIZED);
throw new Exception(Exception::USER_UNAUTHORIZED, 'Write permissions must be one of: (' . \implode(', ', $roles) . ')');
}
}
}
@@ -2382,11 +2387,11 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum
*/
$document->setAttribute('$collection', $collectionId);
} catch (AuthorizationException $exception) {
throw new Exception('Unauthorized permissions', 401, Exception::USER_UNAUTHORIZED);
throw new Exception(Exception::USER_UNAUTHORIZED);
} catch (DuplicateException $exception) {
throw new Exception('Document already exists', 409, Exception::DOCUMENT_ALREADY_EXISTS);
throw new Exception(Exception::DOCUMENT_ALREADY_EXISTS);
} catch (StructureException $exception) {
throw new Exception($exception->getMessage(), 400, Exception::DOCUMENT_INVALID_STRUCTURE);
throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, $exception->getMessage());
}
$events
@@ -2438,7 +2443,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($database->isEmpty()) {
throw new Exception('Database not found', 404, Exception::DATABASE_NOT_FOUND);
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
/**
* Skip Authorization to get the collection. Needed in case of empty permissions for document level permissions.
@@ -2447,7 +2452,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu
if ($collection->isEmpty() || !$collection->getAttribute('enabled')) {
if (!($mode === APP_MODE_ADMIN && Auth::isPrivilegedUser(Authorization::getRoles()))) {
throw new Exception('Collection not found', 404, Exception::COLLECTION_NOT_FOUND);
throw new Exception(Exception::COLLECTION_NOT_FOUND);
}
}
@@ -2455,7 +2460,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu
if ($collection->getAttribute('permission') === 'collection') {
$validator = new Authorization('write');
if (!$validator->isValid($collection->getWrite())) {
throw new Exception('Unauthorized permissions', 401, Exception::USER_UNAUTHORIZED);
throw new Exception(Exception::USER_UNAUTHORIZED);
}
}
@@ -2467,7 +2472,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu
}
if ($document->isEmpty()) {
throw new Exception('No document found', 404, Exception::DOCUMENT_NOT_FOUND);
throw new Exception(Exception::DOCUMENT_NOT_FOUND);
}
if ($collection->getAttribute('permission') === 'collection') {
@@ -2762,7 +2767,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/usage')
$collection = $dbForProject->getCollection('database_' . $database->getInternalId() . '_collection_' . $collectionDocument->getInternalId());
if ($collection->isEmpty()) {
throw new Exception('Collection not found', 404, Exception::COLLECTION_NOT_FOUND);
throw new Exception(Exception::COLLECTION_NOT_FOUND);
}
$usage = [];