Merge pull request #8917 from appwrite/chore-update-database

Update database
This commit is contained in:
Jake Barnby 2024-11-04 22:43:42 +13:00 committed by GitHub
commit 5d4ae8075a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 160 additions and 155 deletions

View file

@ -3,7 +3,6 @@
use Appwrite\Auth\Auth;
use Appwrite\Detector\Detector;
use Appwrite\Event\Database as EventDatabase;
use Appwrite\Event\Delete;
use Appwrite\Event\Event;
use Appwrite\Event\Usage;
use Appwrite\Extend\Exception;
@ -26,6 +25,7 @@ use Utopia\Database\Exception\Authorization as AuthorizationException;
use Utopia\Database\Exception\Conflict as ConflictException;
use Utopia\Database\Exception\Duplicate as DuplicateException;
use Utopia\Database\Exception\Limit as LimitException;
use Utopia\Database\Exception\NotFound as NotFoundException;
use Utopia\Database\Exception\Query as QueryException;
use Utopia\Database\Exception\Restricted as RestrictedException;
use Utopia\Database\Exception\Structure as StructureException;
@ -352,13 +352,16 @@ function updateAttribute(
if ($type === Database::VAR_RELATIONSHIP) {
$primaryDocumentOptions = \array_merge($attribute->getAttribute('options', []), $options);
$attribute->setAttribute('options', $primaryDocumentOptions);
$dbForProject->updateRelationship(
collection: $collectionId,
id: $key,
newKey: $newKey,
onDelete: $primaryDocumentOptions['onDelete'],
);
try {
$dbForProject->updateRelationship(
collection: $collectionId,
id: $key,
newKey: $newKey,
onDelete: $primaryDocumentOptions['onDelete'],
);
} catch (NotFoundException) {
throw new Exception(Exception::ATTRIBUTE_NOT_FOUND);
}
if ($primaryDocumentOptions['twoWay']) {
$relatedCollection = $dbForProject->getDocument('database_' . $db->getInternalId(), $primaryDocumentOptions['relatedCollection']);
@ -388,6 +391,8 @@ function updateAttribute(
);
} catch (TruncateException) {
throw new Exception(Exception::ATTRIBUTE_INVALID_RESIZE);
} catch (NotFoundException) {
throw new Exception(Exception::ATTRIBUTE_NOT_FOUND);
}
}
@ -530,12 +535,7 @@ App::get('/v1/databases')
->inject('response')
->inject('dbForProject')
->action(function (array $queries, string $search, Response $response, Database $dbForProject) {
try {
$queries = Query::parseQueries($queries);
} catch (QueryException $e) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
$queries = Query::parseQueries($queries);
if (!empty($search)) {
$queries[] = Query::search('search', $search);
@ -849,7 +849,7 @@ App::post('/v1/databases/:databaseId/collections')
});
App::get('/v1/databases/:databaseId/collections')
->alias('/v1/database/collections', ['databaseId' => 'default'])
->alias('/v1/database/collections')
->desc('List collections')
->groups(['api', 'database'])
->label('scope', 'collections.read')
@ -875,11 +875,7 @@ App::get('/v1/databases/:databaseId/collections')
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
try {
$queries = Query::parseQueries($queries);
} catch (QueryException $e) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
$queries = Query::parseQueries($queries);
if (!empty($search)) {
$queries[] = Query::search('search', $search);
@ -919,7 +915,7 @@ App::get('/v1/databases/:databaseId/collections')
});
App::get('/v1/databases/:databaseId/collections/:collectionId')
->alias('/v1/database/collections/:collectionId', ['databaseId' => 'default'])
->alias('/v1/database/collections/:collectionId')
->desc('Get collection')
->groups(['api', 'database'])
->label('scope', 'collections.read')
@ -954,7 +950,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId')
});
App::get('/v1/databases/:databaseId/collections/:collectionId/logs')
->alias('/v1/database/collections/:collectionId/logs', ['databaseId' => 'default'])
->alias('/v1/database/collections/:collectionId/logs')
->desc('List collection logs')
->groups(['api', 'database'])
->label('scope', 'collections.read')
@ -988,12 +984,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/logs')
throw new Exception(Exception::COLLECTION_NOT_FOUND);
}
try {
$queries = Query::parseQueries($queries);
} catch (QueryException $e) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
$queries = Query::parseQueries($queries);
$grouped = Query::groupByType($queries);
$limit = $grouped['limit'] ?? APP_LIMIT_COUNT;
$offset = $grouped['offset'] ?? 0;
@ -1055,7 +1046,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/logs')
App::put('/v1/databases/:databaseId/collections/:collectionId')
->alias('/v1/database/collections/:collectionId', ['databaseId' => 'default'])
->alias('/v1/database/collections/:collectionId')
->desc('Update collection')
->groups(['api', 'database', 'schema'])
->label('scope', 'collections.write')
@ -1101,12 +1092,16 @@ App::put('/v1/databases/:databaseId/collections/:collectionId')
$enabled ??= $collection->getAttribute('enabled', true);
$collection = $dbForProject->updateDocument('database_' . $database->getInternalId(), $collectionId, $collection
$collection = $dbForProject->updateDocument(
'database_' . $database->getInternalId(),
$collectionId,
$collection
->setAttribute('name', $name)
->setAttribute('$permissions', $permissions)
->setAttribute('documentSecurity', $documentSecurity)
->setAttribute('enabled', $enabled)
->setAttribute('search', implode(' ', [$collectionId, $name])));
->setAttribute('search', \implode(' ', [$collectionId, $name]))
);
$dbForProject->updateCollection('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $permissions, $documentSecurity);
@ -1119,7 +1114,7 @@ App::put('/v1/databases/:databaseId/collections/:collectionId')
});
App::delete('/v1/databases/:databaseId/collections/:collectionId')
->alias('/v1/database/collections/:collectionId', ['databaseId' => 'default'])
->alias('/v1/database/collections/:collectionId')
->desc('Delete collection')
->groups(['api', 'database', 'schema'])
->label('scope', 'collections.write')
@ -1175,7 +1170,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId')
});
App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/string')
->alias('/v1/database/collections/:collectionId/attributes/string', ['databaseId' => 'default'])
->alias('/v1/database/collections/:collectionId/attributes/string')
->desc('Create string attribute')
->groups(['api', 'database', 'schema'])
->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create')
@ -1226,14 +1221,13 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/string
'filters' => $filters,
]), $response, $dbForProject, $queueForDatabase, $queueForEvents);
$response
->setStatusCode(Response::STATUS_CODE_ACCEPTED)
->dynamic($attribute, Response::MODEL_ATTRIBUTE_STRING);
});
App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/email')
->alias('/v1/database/collections/:collectionId/attributes/email', ['databaseId' => 'default'])
->alias('/v1/database/collections/:collectionId/attributes/email')
->desc('Create email attribute')
->groups(['api', 'database', 'schema'])
->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create')
@ -1276,7 +1270,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/email'
});
App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/enum')
->alias('/v1/database/collections/:collectionId/attributes/enum', ['databaseId' => 'default'])
->alias('/v1/database/collections/:collectionId/attributes/enum')
->desc('Create enum attribute')
->groups(['api', 'database', 'schema'])
->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create')
@ -1324,7 +1318,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/enum')
});
App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/ip')
->alias('/v1/database/collections/:collectionId/attributes/ip', ['databaseId' => 'default'])
->alias('/v1/database/collections/:collectionId/attributes/ip')
->desc('Create IP address attribute')
->groups(['api', 'database', 'schema'])
->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create')
@ -1367,7 +1361,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/ip')
});
App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/url')
->alias('/v1/database/collections/:collectionId/attributes/url', ['databaseId' => 'default'])
->alias('/v1/database/collections/:collectionId/attributes/url')
->desc('Create URL attribute')
->groups(['api', 'database', 'schema'])
->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create')
@ -1410,7 +1404,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/url')
});
App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/integer')
->alias('/v1/database/collections/:collectionId/attributes/integer', ['databaseId' => 'default'])
->alias('/v1/database/collections/:collectionId/attributes/integer')
->desc('Create integer attribute')
->groups(['api', 'database', 'schema'])
->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create')
@ -1440,8 +1434,8 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/intege
->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?int $min, ?int $max, ?int $default, bool $array, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents) {
// Ensure attribute default is within range
$min = (is_null($min)) ? PHP_INT_MIN : \intval($min);
$max = (is_null($max)) ? PHP_INT_MAX : \intval($max);
$min = \is_null($min) ? PHP_INT_MIN : $min;
$max = \is_null($max) ? PHP_INT_MAX : $max;
if ($min > $max) {
throw new Exception(Exception::ATTRIBUTE_VALUE_INVALID, 'Minimum value must be lesser than maximum value');
@ -1482,7 +1476,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/intege
});
App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/float')
->alias('/v1/database/collections/:collectionId/attributes/float', ['databaseId' => 'default'])
->alias('/v1/database/collections/:collectionId/attributes/float')
->desc('Create float attribute')
->groups(['api', 'database', 'schema'])
->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create')
@ -1512,21 +1506,16 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/float'
->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?float $min, ?float $max, ?float $default, bool $array, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents) {
// Ensure attribute default is within range
$min = (is_null($min)) ? -PHP_FLOAT_MAX : \floatval($min);
$max = (is_null($max)) ? PHP_FLOAT_MAX : \floatval($max);
$min = \is_null($min) ? -PHP_FLOAT_MAX : $min;
$max = \is_null($max) ? PHP_FLOAT_MAX : $max;
if ($min > $max) {
throw new Exception(Exception::ATTRIBUTE_VALUE_INVALID, 'Minimum value must be lesser than maximum value');
}
// Ensure default value is a float
if (!is_null($default)) {
$default = \floatval($default);
}
$validator = new Range($min, $max, Database::VAR_FLOAT);
if (!is_null($default) && !$validator->isValid($default)) {
if (!\is_null($default) && !$validator->isValid($default)) {
throw new Exception(Exception::ATTRIBUTE_VALUE_INVALID, $validator->getDescription());
}
@ -1557,7 +1546,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/float'
});
App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/boolean')
->alias('/v1/database/collections/:collectionId/attributes/boolean', ['databaseId' => 'default'])
->alias('/v1/database/collections/:collectionId/attributes/boolean')
->desc('Create boolean attribute')
->groups(['api', 'database', 'schema'])
->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create')
@ -1599,7 +1588,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/boolea
});
App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/datetime')
->alias('/v1/database/collections/:collectionId/attributes/datetime', ['databaseId' => 'default'])
->alias('/v1/database/collections/:collectionId/attributes/datetime')
->desc('Create datetime attribute')
->groups(['api', 'database'])
->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create')
@ -1618,7 +1607,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/dateti
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
->param('key', '', new Key(), 'Attribute Key.')
->param('required', null, new Boolean(), 'Is attribute required?')
->param('default', null, new DatetimeValidator(), 'Default value for the attribute in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. Cannot be set when attribute is required.', true)
->param('default', null, fn (Database $dbForProject) => new DatetimeValidator($dbForProject->getAdapter()->getMinDateTime(), $dbForProject->getAdapter()->getMaxDateTime()), 'Default value for the attribute in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. Cannot be set when attribute is required.', true, ['dbForProject'])
->param('array', false, new Boolean(), 'Is attribute an array?', true)
->inject('response')
->inject('dbForProject')
@ -1644,7 +1633,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/dateti
});
App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/relationship')
->alias('/v1/database/collections/:collectionId/attributes/relationship', ['databaseId' => 'default'])
->alias('/v1/database/collections/:collectionId/attributes/relationship')
->desc('Create relationship attribute')
->groups(['api', 'database'])
->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create')
@ -1773,7 +1762,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/relati
});
App::get('/v1/databases/:databaseId/collections/:collectionId/attributes')
->alias('/v1/database/collections/:collectionId/attributes', ['databaseId' => 'default'])
->alias('/v1/database/collections/:collectionId/attributes')
->desc('List attributes')
->groups(['api', 'database'])
->label('scope', 'collections.read')
@ -1804,16 +1793,12 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/attributes')
throw new Exception(Exception::COLLECTION_NOT_FOUND);
}
try {
$queries = Query::parseQueries($queries);
} catch (QueryException $e) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
$queries = Query::parseQueries($queries);
\array_push(
$queries,
Query::equal('databaseInternalId', [$database->getInternalId()]),
Query::equal('collectionInternalId', [$collection->getInternalId()]),
Query::equal('databaseInternalId', [$database->getInternalId()])
);
/**
@ -1822,6 +1807,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/attributes')
$cursor = \array_filter($queries, function ($query) {
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
});
$cursor = \reset($cursor);
if ($cursor) {
@ -1832,8 +1818,8 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/attributes')
$attributeId = $cursor->getValue();
$cursorDocument = Authorization::skip(fn () => $dbForProject->find('attributes', [
Query::equal('collectionInternalId', [$collection->getInternalId()]),
Query::equal('databaseInternalId', [$database->getInternalId()]),
Query::equal('collectionInternalId', [$collection->getInternalId()]),
Query::equal('key', [$attributeId]),
Query::limit(1),
]));
@ -1857,7 +1843,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/attributes')
});
App::get('/v1/databases/:databaseId/collections/:collectionId/attributes/:key')
->alias('/v1/database/collections/:collectionId/attributes/:key', ['databaseId' => 'default'])
->alias('/v1/database/collections/:collectionId/attributes/:key')
->desc('Get attribute')
->groups(['api', 'database'])
->label('scope', 'collections.read')
@ -2309,7 +2295,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/datet
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
->param('key', '', new Key(), 'Attribute Key.')
->param('required', null, new Boolean(), 'Is attribute required?')
->param('default', null, new Nullable(new DatetimeValidator()), 'Default value for attribute when not provided. Cannot be set when attribute is required.')
->param('default', null, fn (Database $dbForProject) => new Nullable(new DatetimeValidator($dbForProject->getAdapter()->getMinDateTime(), $dbForProject->getAdapter()->getMaxDateTime())), 'Default value for attribute when not provided. Cannot be set when attribute is required.', injections: ['dbForProject'])
->param('newKey', null, new Key(), 'New attribute key.', true)
->inject('response')
->inject('dbForProject')
@ -2390,7 +2376,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/:key/
});
App::delete('/v1/databases/:databaseId/collections/:collectionId/attributes/:key')
->alias('/v1/database/collections/:collectionId/attributes/:key', ['databaseId' => 'default'])
->alias('/v1/database/collections/:collectionId/attributes/:key')
->desc('Delete attribute')
->groups(['api', 'database', 'schema'])
->label('scope', 'collections.write')
@ -2504,7 +2490,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/attributes/:key
});
App::post('/v1/databases/:databaseId/collections/:collectionId/indexes')
->alias('/v1/database/collections/:collectionId/indexes', ['databaseId' => 'default'])
->alias('/v1/database/collections/:collectionId/indexes')
->desc('Create index')
->groups(['api', 'database'])
->label('event', 'databases.[databaseId].collections.[collectionId].indexes.[indexId].create')
@ -2675,7 +2661,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/indexes')
});
App::get('/v1/databases/:databaseId/collections/:collectionId/indexes')
->alias('/v1/database/collections/:collectionId/indexes', ['databaseId' => 'default'])
->alias('/v1/database/collections/:collectionId/indexes')
->desc('List indexes')
->groups(['api', 'database'])
->label('scope', 'collections.read')
@ -2706,13 +2692,13 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/indexes')
throw new Exception(Exception::COLLECTION_NOT_FOUND);
}
try {
$queries = Query::parseQueries($queries);
} catch (QueryException $e) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
$queries = Query::parseQueries($queries);
\array_push($queries, Query::equal('collectionId', [$collectionId]), Query::equal('databaseId', [$databaseId]));
\array_push(
$queries,
Query::equal('databaseId', [$databaseId]),
Query::equal('collectionId', [$collectionId]),
);
/**
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
@ -3042,10 +3028,12 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents')
try {
$document = $dbForProject->createDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $document);
} catch (StructureException $exception) {
throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, $exception->getMessage());
} catch (DuplicateException $exception) {
} catch (StructureException $e) {
throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, $e->getMessage());
} catch (DuplicateException $e) {
throw new Exception(Exception::DOCUMENT_ALREADY_EXISTS);
} catch (NotFoundException $e) {
throw new Exception(Exception::COLLECTION_NOT_FOUND);
}
// Add $collectionId and $databaseId for all documents
@ -3176,14 +3164,8 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents')
$cursor->setValue($cursorDocument);
}
try {
$documents = $dbForProject->find('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $queries);
$total = $dbForProject->count('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $queries, APP_LIMIT_COUNT);
} catch (AuthorizationException) {
throw new Exception(Exception::USER_UNAUTHORIZED);
} catch (QueryException $e) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
$documents = $dbForProject->find('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $queries);
$total = $dbForProject->count('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $queries, APP_LIMIT_COUNT);
// Add $collectionId and $databaseId for all documents
$processDocument = (function (Document $collection, Document $document) use (&$processDocument, $dbForProject, $database): bool {
@ -3469,7 +3451,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen
});
App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:documentId')
->alias('/v1/database/collections/:collectionId/documents/:documentId', ['databaseId' => 'default'])
->alias('/v1/database/collections/:collectionId/documents/:documentId')
->desc('Update document')
->groups(['api', 'database'])
->label('event', 'databases.[databaseId].collections.[collectionId].documents.[documentId].update')
@ -3647,8 +3629,10 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum
throw new Exception(Exception::USER_UNAUTHORIZED);
} catch (DuplicateException) {
throw new Exception(Exception::DOCUMENT_ALREADY_EXISTS);
} catch (StructureException $exception) {
throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, $exception->getMessage());
} catch (StructureException $e) {
throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, $e->getMessage());
} catch (NotFoundException $e) {
throw new Exception(Exception::COLLECTION_NOT_FOUND);
}
// Add $collectionId and $databaseId for all documents
@ -3757,12 +3741,16 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu
throw new Exception(Exception::DOCUMENT_NOT_FOUND);
}
$dbForProject->withRequestTimestamp($requestTimestamp, function () use ($dbForProject, $database, $collection, $documentId) {
$dbForProject->deleteDocument(
'database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(),
$documentId
);
});
try {
$dbForProject->withRequestTimestamp($requestTimestamp, function () use ($dbForProject, $database, $collection, $documentId) {
$dbForProject->deleteDocument(
'database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(),
$documentId
);
});
} catch (NotFoundException $e) {
throw new Exception(Exception::COLLECTION_NOT_FOUND);
}
// Add $collectionId and $databaseId for all documents
$processDocument = function (Document $collection, Document $document) use (&$processDocument, $dbForProject, $database) {

View file

@ -1753,7 +1753,7 @@ App::post('/v1/functions/:functionId/executions')
->param('path', '/', new Text(2048), 'HTTP path of execution. Path can include query params. Default value is /', true)
->param('method', 'POST', new Whitelist(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'], true), 'HTTP method of execution. Default value is GET.', true)
->param('headers', [], new AnyOf([new Assoc(), new Text(65535)], AnyOf::TYPE_MIXED), 'HTTP headers of execution. Defaults to empty.', true)
->param('scheduledAt', null, new DatetimeValidator(true, DateTimeValidator::PRECISION_MINUTES, 60), 'Scheduled execution time in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future with precision in minutes.', true)
->param('scheduledAt', null, new DatetimeValidator(requireDateInFuture: true, precision: DateTimeValidator::PRECISION_MINUTES, offset: 60), 'Scheduled execution time in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future with precision in minutes.', true)
->inject('response')
->inject('request')
->inject('project')
@ -2642,9 +2642,11 @@ App::get('/v1/functions/templates/:templateId')
->action(function (string $templateId, Response $response) {
$templates = Config::getParam('function-templates', []);
$template = array_shift(\array_filter($templates, function ($template) use ($templateId) {
$filtered = \array_filter($templates, function ($template) use ($templateId) {
return $template['id'] === $templateId;
}));
});
$template = array_shift($filtered);
if (empty($template)) {
throw new Exception(Exception::FUNCTION_TEMPLATE_NOT_FOUND);

View file

@ -16,7 +16,8 @@ use Utopia\App;
use Utopia\Config\Config;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Exception\Duplicate;
use Utopia\Database\Exception\Duplicate as DuplicateException;
use Utopia\Database\Exception\NotFound as NotFoundException;
use Utopia\Database\Exception\Query as QueryException;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
@ -131,7 +132,7 @@ App::post('/v1/storage/buckets')
$bucket = $dbForProject->getDocument('buckets', $bucketId);
$dbForProject->createCollection('bucket_' . $bucket->getInternalId(), $attributes, $indexes, permissions: $permissions ?? [], documentSecurity: $fileSecurity);
} catch (Duplicate) {
} catch (DuplicateException) {
throw new Exception(Exception::STORAGE_BUCKET_ALREADY_EXISTS);
}
@ -273,10 +274,6 @@ App::put('/v1/storage/buckets/:bucketId')
$encryption ??= $bucket->getAttribute('encryption', true);
$antivirus ??= $bucket->getAttribute('antivirus', true);
/**
* Map aggregate permissions into the multiple permissions they represent,
* accounting for the resource type given that some types not allowed specific permissions.
*/
// Map aggregate permissions into the multiple permissions they represent.
$permissions = Permission::aggregate($permissions);
@ -290,11 +287,11 @@ App::put('/v1/storage/buckets/:bucketId')
->setAttribute('encryption', $encryption)
->setAttribute('compression', $compression)
->setAttribute('antivirus', $antivirus));
$dbForProject->updateCollection('bucket_' . $bucket->getInternalId(), $permissions, $fileSecurity);
$queueForEvents
->setParam('bucketId', $bucket->getId())
;
->setParam('bucketId', $bucket->getId());
$response->dynamic($bucket, Response::MODEL_BUCKET);
});
@ -342,7 +339,7 @@ App::delete('/v1/storage/buckets/:bucketId')
});
App::post('/v1/storage/buckets/:bucketId/files')
->alias('/v1/storage/files', ['bucketId' => 'default'])
->alias('/v1/storage/files')
->desc('Create file')
->groups(['api', 'storage'])
->label('scope', 'files.write')
@ -670,7 +667,11 @@ App::post('/v1/storage/buckets/:bucketId/files')
'metadata' => $metadata,
]);
$file = $dbForProject->createDocument('bucket_' . $bucket->getInternalId(), $doc);
try {
$file = $dbForProject->createDocument('bucket_' . $bucket->getInternalId(), $doc);
} catch (NotFoundException) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
}
} else {
$file = $file
->setAttribute('chunksUploaded', $chunksUploaded)
@ -686,15 +687,19 @@ App::post('/v1/storage/buckets/:bucketId/files')
if (!$validator->isValid($bucket->getCreate())) {
throw new Exception(Exception::USER_UNAUTHORIZED);
}
$file = Authorization::skip(fn () => $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file));
try {
$file = Authorization::skip(fn () => $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file));
} catch (NotFoundException) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
}
}
}
$queueForEvents
->setParam('bucketId', $bucket->getId())
->setParam('fileId', $file->getId())
->setContext('bucket', $bucket)
;
->setContext('bucket', $bucket);
$metadata = null; // was causing leaks as it was passed by reference
@ -704,7 +709,7 @@ App::post('/v1/storage/buckets/:bucketId/files')
});
App::get('/v1/storage/buckets/:bucketId/files')
->alias('/v1/storage/files', ['bucketId' => 'default'])
->alias('/v1/storage/files')
->desc('List files')
->groups(['api', 'storage'])
->label('scope', 'files.read')
@ -739,11 +744,7 @@ App::get('/v1/storage/buckets/:bucketId/files')
throw new Exception(Exception::USER_UNAUTHORIZED);
}
try {
$queries = Query::parseQueries($queries);
} catch (QueryException $e) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
$queries = Query::parseQueries($queries);
if (!empty($search)) {
$queries[] = Query::search('search', $search);
@ -781,12 +782,16 @@ App::get('/v1/storage/buckets/:bucketId/files')
$filterQueries = Query::groupByType($queries)['filters'];
if ($fileSecurity && !$valid) {
$files = $dbForProject->find('bucket_' . $bucket->getInternalId(), $queries);
$total = $dbForProject->count('bucket_' . $bucket->getInternalId(), $filterQueries, APP_LIMIT_COUNT);
} else {
$files = Authorization::skip(fn () => $dbForProject->find('bucket_' . $bucket->getInternalId(), $queries));
$total = Authorization::skip(fn () => $dbForProject->count('bucket_' . $bucket->getInternalId(), $filterQueries, APP_LIMIT_COUNT));
try {
if ($fileSecurity && !$valid) {
$files = $dbForProject->find('bucket_' . $bucket->getInternalId(), $queries);
$total = $dbForProject->count('bucket_' . $bucket->getInternalId(), $filterQueries, APP_LIMIT_COUNT);
} else {
$files = Authorization::skip(fn () => $dbForProject->find('bucket_' . $bucket->getInternalId(), $queries));
$total = Authorization::skip(fn () => $dbForProject->count('bucket_' . $bucket->getInternalId(), $filterQueries, APP_LIMIT_COUNT));
}
} catch (NotFoundException) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
}
$response->dynamic(new Document([
@ -796,7 +801,7 @@ App::get('/v1/storage/buckets/:bucketId/files')
});
App::get('/v1/storage/buckets/:bucketId/files/:fileId')
->alias('/v1/storage/files/:fileId', ['bucketId' => 'default'])
->alias('/v1/storage/files/:fileId')
->desc('Get file')
->groups(['api', 'storage'])
->label('scope', 'files.read')
@ -844,7 +849,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId')
});
App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
->alias('/v1/storage/files/:fileId/preview', ['bucketId' => 'default'])
->alias('/v1/storage/files/:fileId/preview')
->desc('Get file preview')
->groups(['api', 'storage'])
->label('scope', 'files.read')
@ -1017,7 +1022,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
});
App::get('/v1/storage/buckets/:bucketId/files/:fileId/download')
->alias('/v1/storage/files/:fileId/download', ['bucketId' => 'default'])
->alias('/v1/storage/files/:fileId/download')
->desc('Get file for download')
->groups(['api', 'storage'])
->label('scope', 'files.read')
@ -1158,7 +1163,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download')
});
App::get('/v1/storage/buckets/:bucketId/files/:fileId/view')
->alias('/v1/storage/files/:fileId/view', ['bucketId' => 'default'])
->alias('/v1/storage/files/:fileId/view')
->desc('Get file for view')
->groups(['api', 'storage'])
->label('scope', 'files.read')
@ -1465,7 +1470,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/push')
});
App::put('/v1/storage/buckets/:bucketId/files/:fileId')
->alias('/v1/storage/files/:fileId', ['bucketId' => 'default'])
->alias('/v1/storage/files/:fileId')
->desc('Update file')
->groups(['api', 'storage'])
->label('scope', 'files.write')
@ -1555,10 +1560,14 @@ App::put('/v1/storage/buckets/:bucketId/files/:fileId')
$file->setAttribute('name', $name);
}
if ($fileSecurity && !$valid) {
$file = $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file);
} else {
$file = Authorization::skip(fn () => $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file));
try {
if ($fileSecurity && !$valid) {
$file = $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file);
} else {
$file = Authorization::skip(fn () => $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file));
}
} catch (NotFoundException) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
}
$queueForEvents
@ -1641,10 +1650,14 @@ App::delete('/v1/storage/buckets/:bucketId/files/:fileId')
->setResource('file/' . $fileId)
;
if ($fileSecurity && !$valid) {
$deleted = $dbForProject->deleteDocument('bucket_' . $bucket->getInternalId(), $fileId);
} else {
$deleted = Authorization::skip(fn () => $dbForProject->deleteDocument('bucket_' . $bucket->getInternalId(), $fileId));
try {
if ($fileSecurity && !$valid) {
$deleted = $dbForProject->deleteDocument('bucket_' . $bucket->getInternalId(), $fileId);
} else {
$deleted = Authorization::skip(fn () => $dbForProject->deleteDocument('bucket_' . $bucket->getInternalId(), $fileId));
}
} catch (NotFoundException) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
}
if (!$deleted) {

View file

@ -775,6 +775,8 @@ App::error()
case 'Utopia\Database\Exception\Relationship':
$error = new AppwriteException(AppwriteException::RELATIONSHIP_VALUE_INVALID, $error->getMessage(), previous: $error);
break;
case 'Utopia\Database\Exception\NotFound':
$error = new AppwriteException(AppwriteException::COLLECTION_NOT_FOUND, $error->getMessage(), previous: $error);
}
$code = $error->getCode();

View file

@ -51,7 +51,7 @@
"utopia-php/cache": "0.10.*",
"utopia-php/cli": "0.15.*",
"utopia-php/config": "0.2.*",
"utopia-php/database": "0.53.9",
"utopia-php/database": "0.53.12",
"utopia-php/domains": "0.5.*",
"utopia-php/dsn": "0.2.1",
"utopia-php/framework": "0.33.*",

40
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "1529d4abfe0432b2d9c838705220ed4a",
"content-hash": "102bab0efa76adc92312b9c5a7f488e3",
"packages": [
{
"name": "adhocore/jwt",
@ -1724,32 +1724,32 @@
},
{
"name": "utopia-php/database",
"version": "0.53.9",
"version": "0.53.12",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/database.git",
"reference": "19969d2c6d29b5d1cbf4cb1a33e18017a54f30e3"
"reference": "4068d0c5c503ec4562f8c0aa9cfd095889418b83"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/database/zipball/19969d2c6d29b5d1cbf4cb1a33e18017a54f30e3",
"reference": "19969d2c6d29b5d1cbf4cb1a33e18017a54f30e3",
"url": "https://api.github.com/repos/utopia-php/database/zipball/4068d0c5c503ec4562f8c0aa9cfd095889418b83",
"reference": "4068d0c5c503ec4562f8c0aa9cfd095889418b83",
"shasum": ""
},
"require": {
"ext-mbstring": "*",
"ext-pdo": "*",
"php": ">=8.0",
"php": ">=8.3",
"utopia-php/cache": "0.10.*",
"utopia-php/framework": "0.33.*",
"utopia-php/mongo": "0.3.*"
},
"require-dev": {
"fakerphp/faker": "1.23.*",
"laravel/pint": "1.17.*",
"pcov/clobber": "2.0.*",
"phpstan/phpstan": "1.11.*",
"phpunit/phpunit": "9.6.*",
"laravel/pint": "1.*",
"pcov/clobber": "2.*",
"phpstan/phpstan": "1.*",
"phpunit/phpunit": "9.*",
"rregeer/phpunit-coverage-check": "0.3.*",
"swoole/ide-helper": "5.1.3",
"utopia-php/cli": "0.14.*"
@ -1774,9 +1774,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/database/issues",
"source": "https://github.com/utopia-php/database/tree/0.53.9"
"source": "https://github.com/utopia-php/database/tree/0.53.12"
},
"time": "2024-10-31T08:18:52+00:00"
"time": "2024-11-04T07:51:44+00:00"
},
{
"name": "utopia-php/domains",
@ -4068,23 +4068,23 @@
},
{
"name": "phpdocumentor/type-resolver",
"version": "1.8.2",
"version": "1.9.0",
"source": {
"type": "git",
"url": "https://github.com/phpDocumentor/TypeResolver.git",
"reference": "153ae662783729388a584b4361f2545e4d841e3c"
"reference": "1fb5ba8d045f5dd984ebded5b1cc66f29459422d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/153ae662783729388a584b4361f2545e4d841e3c",
"reference": "153ae662783729388a584b4361f2545e4d841e3c",
"url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/1fb5ba8d045f5dd984ebded5b1cc66f29459422d",
"reference": "1fb5ba8d045f5dd984ebded5b1cc66f29459422d",
"shasum": ""
},
"require": {
"doctrine/deprecations": "^1.0",
"php": "^7.3 || ^8.0",
"phpdocumentor/reflection-common": "^2.0",
"phpstan/phpdoc-parser": "^1.13"
"phpstan/phpdoc-parser": "^1.18"
},
"require-dev": {
"ext-tokenizer": "*",
@ -4120,9 +4120,9 @@
"description": "A PSR-5 based resolver of Class names, Types and Structural Element Names",
"support": {
"issues": "https://github.com/phpDocumentor/TypeResolver/issues",
"source": "https://github.com/phpDocumentor/TypeResolver/tree/1.8.2"
"source": "https://github.com/phpDocumentor/TypeResolver/tree/1.9.0"
},
"time": "2024-02-23T11:10:43+00:00"
"time": "2024-11-03T20:11:34+00:00"
},
{
"name": "phpspec/prophecy",
@ -7005,7 +7005,7 @@
],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": {},
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": {