Merge pull request #2487 from appwrite/feat-database-disable-collections

feat(database): add collection enable/disable
This commit is contained in:
Torsten Dittmann 2021-12-17 12:07:25 +01:00 committed by GitHub
commit d6486ac9e4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 170 additions and 30 deletions

View file

@ -43,6 +43,17 @@ $collections = [
'array' => false,
'filters' => [],
],
[
'$id' => 'enabled',
'type' => Database::VAR_BOOLEAN,
'signed' => true,
'size' => 0,
'format' => '',
'filters' => [],
'required' => true,
'default' => null,
'array' => false,
],
[
'$id' => 'permission',
'type' => Database::VAR_STRING,

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -175,6 +175,7 @@ App::post('/v1/database/collections')
'permission' => $permission, // Permissions model type (document vs collection)
'dateCreated' => time(),
'dateUpdated' => time(),
'enabled' => true,
'name' => $name,
'search' => implode(' ', [$collectionId, $name]),
]));
@ -592,11 +593,12 @@ App::put('/v1/database/collections/:collectionId')
->param('permission', null, new WhiteList(['document', 'collection']), 'Permissions type model to use for reading documents in this collection. You can use collection-level permission set once on the collection using the `read` and `write` params, or you can set document-level permission where each document read and write params will decide who has access to read and write to each document individually. [learn more about permissions](https://appwrite.io/docs/permissions) and get a full list of available permissions.')
->param('read', null, new Permissions(), 'An array of strings with read permissions. By default inherits the existing read permissions. [learn more about permissions](https://appwrite.io/docs/permissions) and get a full list of available permissions.', true)
->param('write', null, new Permissions(), 'An array of strings with write permissions. By default inherits the existing write permissions. [learn more about permissions](https://appwrite.io/docs/permissions) and get a full list of available permissions.', true)
->param('enabled', true, new Boolean(), 'Is collection enabled?', true)
->inject('response')
->inject('dbForInternal')
->inject('audits')
->inject('usage')
->action(function ($collectionId, $name, $permission, $read, $write, $response, $dbForInternal, $audits, $usage) {
->action(function ($collectionId, $name, $permission, $read, $write, $enabled, $response, $dbForInternal, $audits, $usage) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Appwrite\Event\Event $audits */
@ -608,8 +610,9 @@ App::put('/v1/database/collections/:collectionId')
throw new Exception('Collection not found', 404);
}
$read = (is_null($read)) ? ($collection->getRead() ?? []) : $read; // By default inherit read permissions
$write = (is_null($write)) ? ($collection->getWrite() ?? []) : $write; // By default inherit write permissions
$read ??= $collection->getRead() ?? []; // By default inherit read permissions
$write ??= $collection->getWrite() ?? []; // By default inherit write permissions
$enabled ??= $collection->getAttribute('enabled', true);
try {
$collection = $dbForInternal->updateDocument('collections', $collection->getId(), $collection
@ -618,6 +621,7 @@ App::put('/v1/database/collections/:collectionId')
->setAttribute('name', $name)
->setAttribute('permission', $permission)
->setAttribute('dateUpdated', time())
->setAttribute('enabled', $enabled)
->setAttribute('search', implode(' ', [$collectionId, $name]))
);
}
@ -1590,7 +1594,8 @@ App::post('/v1/database/collections/:collectionId/documents')
->inject('audits')
->inject('usage')
->inject('events')
->action(function ($documentId, $collectionId, $data, $read, $write, $response, $dbForInternal, $dbForExternal, $user, $audits, $usage, $events) {
->inject('mode')
->action(function ($documentId, $collectionId, $data, $read, $write, $response, $dbForInternal, $dbForExternal, $user, $audits, $usage, $events, $mode) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForExternal */
@ -1598,6 +1603,7 @@ App::post('/v1/database/collections/:collectionId/documents')
/** @var Appwrite\Event\Event $audits */
/** @var Appwrite\Stats\Stats $usage */
/** @var Appwrite\Event\Event $events */
/** @var string $mode */
$data = (\is_string($data)) ? \json_decode($data, true) : $data; // Cast to JSON array
@ -1614,7 +1620,7 @@ App::post('/v1/database/collections/:collectionId/documents')
*/
$collection = Authorization::skip(fn() => $dbForInternal->getDocument('collections', $collectionId));
if ($collection->isEmpty()) {
if ($collection->isEmpty() || (!$collection->getAttribute('enabled') && $mode !== APP_MODE_ADMIN )) {
throw new Exception('Collection not found', 404);
}
@ -1702,18 +1708,20 @@ App::get('/v1/database/collections/:collectionId/documents')
->inject('dbForInternal')
->inject('dbForExternal')
->inject('usage')
->action(function ($collectionId, $queries, $limit, $offset, $cursor, $cursorDirection, $orderAttributes, $orderTypes, $response, $dbForInternal, $dbForExternal, $usage) {
->inject('mode')
->action(function ($collectionId, $queries, $limit, $offset, $cursor, $cursorDirection, $orderAttributes, $orderTypes, $response, $dbForInternal, $dbForExternal, $usage, $mode) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForExternal */
/** @var Appwrite\Stats\Stats $usage */
/** @var string $mode */
/**
* 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()) {
if ($collection->isEmpty() || (!$collection->getAttribute('enabled') && $mode !== APP_MODE_ADMIN )) {
throw new Exception('Collection not found', 404);
}
@ -1782,17 +1790,19 @@ App::get('/v1/database/collections/:collectionId/documents/:documentId')
->inject('dbForInternal')
->inject('dbForExternal')
->inject('usage')
->action(function ($collectionId, $documentId, $response, $dbForInternal, $dbForExternal, $usage) {
->inject('mode')
->action(function ($collectionId, $documentId, $response, $dbForInternal, $dbForExternal, $usage, $mode) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $$dbForInternal */
/** @var Utopia\Database\Database $dbForExternal */
/** @var string $mode */
/**
* 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()) {
if ($collection->isEmpty() || (!$collection->getAttribute('enabled') && $mode !== APP_MODE_ADMIN )) {
throw new Exception('Collection not found', 404);
}
@ -1940,20 +1950,22 @@ App::patch('/v1/database/collections/:collectionId/documents/:documentId')
->inject('audits')
->inject('usage')
->inject('events')
->action(function ($collectionId, $documentId, $data, $read, $write, $response, $dbForInternal, $dbForExternal, $audits, $usage, $events) {
->inject('mode')
->action(function ($collectionId, $documentId, $data, $read, $write, $response, $dbForInternal, $dbForExternal, $audits, $usage, $events, $mode) {
/** @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 */
/** @var string $mode */
/**
* 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()) {
if ($collection->isEmpty() || (!$collection->getAttribute('enabled') && $mode !== APP_MODE_ADMIN )) {
throw new Exception('Collection not found', 404);
}
@ -2060,19 +2072,21 @@ App::delete('/v1/database/collections/:collectionId/documents/:documentId')
->inject('events')
->inject('audits')
->inject('usage')
->action(function ($collectionId, $documentId, $response, $dbForInternal, $dbForExternal, $events, $audits, $usage) {
->inject('mode')
->action(function ($collectionId, $documentId, $response, $dbForInternal, $dbForExternal, $events, $audits, $usage, $mode) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForExternal */
/** @var Appwrite\Event\Event $events */
/** @var Appwrite\Event\Event $audits */
/** @var Appwrite\Stats\Stats $usage */
/** @var string $mode */
/**
* 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()) {
if ($collection->isEmpty() || (!$collection->getAttribute('enabled') && $mode !== APP_MODE_ADMIN )) {
throw new Exception('Collection not found', 404);
}

View file

@ -795,6 +795,12 @@ App::setResource('dbForConsole', function($db, $cache) {
App::setResource('mode', function($request) {
/** @var Utopia\Swoole\Request $request */
/**
* Defines the mode for the request:
* - 'default' => Requests for Client and Server Side
* - 'admin' => Request from the Console on non-console projects
*/
return $request->getParam('mode', $request->getHeader('x-appwrite-mode', APP_MODE_DEFAULT));
}, ['request']);

View file

@ -456,6 +456,9 @@ $logs = $this->getParam('logs', null);
<label for="collection-name">Name</label>
<input name="name" id="collection-name" type="text" autocomplete="off" data-ls-bind="{{project-collection.name}}" data-forms-text-direction required placeholder="Collection Name" maxlength="128" />
<div class="margin-bottom">
<input name="enabled" type="hidden" data-forms-switch data-cast-to="boolean" data-ls-bind="{{project-collection.enabled}}" /> &nbsp; Enabled <span class="tooltip" data-tooltip="Mark whether collection is enabled"><i class="icon-info-circled"></i></span>
</div>
<label class="margin-bottom-small">Permissions</label>

View file

@ -0,0 +1,14 @@
const sdk = new Appwrite();
sdk
.setEndpoint('https://[HOSTNAME_OR_IP]/v1') // Your API Endpoint
.setProject('5df5acd0d48c2') // Your project ID
;
let promise = sdk.functions.listRuntimes();
promise.then(function (response) {
console.log(response); // Success
}, function (error) {
console.log(error); // Failure
});

View file

@ -5,7 +5,7 @@ sdk
.setProject('5df5acd0d48c2') // Your project ID
;
let promise = sdk.projects.delete('[PROJECT_ID]', '[PASSWORD]');
let promise = sdk.projects.delete('[PROJECT_ID]', 'password');
promise.then(function (response) {
console.log(response); // Success

View file

@ -199,7 +199,7 @@
*
* Use this endpoint to allow a new user to register a new account in your
* project. After the user registration completes successfully, you can use
* the [/account/verification](/docs/client/account#accountCreateVerification)
* the [/account/verfication](/docs/client/account#accountCreateVerification)
* route to start verifying the user email address. To allow the new user to
* login to their new account, you need to create a new [account
* session](/docs/client/account#accountCreateSession).
@ -264,12 +264,14 @@
* Update Account Email
*
* Update currently logged in user account email address. After changing user
* address, user confirmation status is being reset and a new confirmation
* mail is sent. For security measures, user password is required to complete
* this request.
* address, the user confirmation status will get reset. A new confirmation
* email is not sent automatically however you can use the send confirmation
* email endpoint again to send the confirmation email. For security measures,
* user password is required to complete this request.
* This endpoint can also be used to convert an anonymous account to a normal
* one, by passing an email address and a new password.
*
*
* @param {string} email
* @param {string} password
* @throws {AppwriteException}
@ -1165,8 +1167,8 @@
* @param {string} collectionId
* @param {string} name
* @param {string} permission
* @param {string} read
* @param {string} write
* @param {string[]} read
* @param {string[]} write
* @throws {AppwriteException}
* @returns {Promise}
*/
@ -1237,12 +1239,13 @@
* @param {string} collectionId
* @param {string} name
* @param {string} permission
* @param {string} read
* @param {string} write
* @param {string[]} read
* @param {string[]} write
* @param {boolean} enabled
* @throws {AppwriteException}
* @returns {Promise}
*/
updateCollection: (collectionId, name, permission, read, write) => __awaiter(this, void 0, void 0, function* () {
updateCollection: (collectionId, name, permission, read, write, enabled) => __awaiter(this, void 0, void 0, function* () {
if (typeof collectionId === 'undefined') {
throw new AppwriteException('Missing required parameter: "collectionId"');
}
@ -1266,6 +1269,9 @@
if (typeof write !== 'undefined') {
payload['write'] = write;
}
if (typeof enabled !== 'undefined') {
payload['enabled'] = enabled;
}
const uri = new URL(this.config.endpoint + path);
return yield this.call('put', uri, {
'content-type': 'application/json',
@ -1793,8 +1799,8 @@
* @param {string} collectionId
* @param {string} documentId
* @param {object} data
* @param {string} read
* @param {string} write
* @param {string[]} read
* @param {string[]} write
* @throws {AppwriteException}
* @returns {Promise}
*/
@ -1861,8 +1867,8 @@
* @param {string} collectionId
* @param {string} documentId
* @param {object} data
* @param {string} read
* @param {string} write
* @param {string[]} read
* @param {string[]} write
* @throws {AppwriteException}
* @returns {Promise}
*/
@ -2234,6 +2240,22 @@
'content-type': 'application/json',
}, payload);
}),
/**
* List the currently active function runtimes.
*
* Get a list of all runtimes that are currently active in your project.
*
* @throws {AppwriteException}
* @returns {Promise}
*/
listRuntimes: () => __awaiter(this, void 0, void 0, function* () {
let path = '/functions/runtimes';
let payload = {};
const uri = new URL(this.config.endpoint + path);
return yield this.call('get', uri, {
'content-type': 'application/json',
}, payload);
}),
/**
* Get Function
*

View file

@ -36,6 +36,12 @@ class Collection extends Model
'default' => '',
'example' => 'My Collection',
])
->addRule('enabled', [
'type' => self::TYPE_BOOLEAN,
'description' => 'Collection enabled.',
'default' => true,
'example' => false,
])
->addRule('permission', [
'type' => self::TYPE_STRING,
'description' => 'Collection permission model. Possible values: `document` or `collection`',

View file

@ -30,6 +30,70 @@ trait DatabaseBase
return ['moviesId' => $movies['body']['$id']];
}
/**
* @depends testCreateCollection
*/
public function testDisableCollection(array $data): void
{
/**
* Test for SUCCESS
*/
$response = $this->client->call(Client::METHOD_PUT, '/database/collections/' . $data['moviesId'], array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'name' => 'Movies',
'enabled' => false,
'permission' => 'document',
]);
$this->assertEquals($response['headers']['status-code'], 200);
$this->assertFalse($response['body']['enabled']);
if ($this->getSide() === 'client') {
$responseCreateDocument = $this->client->call(Client::METHOD_POST, '/database/collections/' . $data['moviesId'] . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'documentId' => 'unique()',
'data' => [
'title' => 'Captain America',
],
'read' => ['user:'.$this->getUser()['$id']],
'write' => ['user:'.$this->getUser()['$id']],
]);
$responseListDocument = $this->client->call(Client::METHOD_GET, '/database/collections/' . $data['moviesId'] . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$responseGetDocument = $this->client->call(Client::METHOD_GET, '/database/collections/' . $data['moviesId'] . '/documents/someID', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$this->assertEquals($responseCreateDocument['headers']['status-code'], 404);
$this->assertEquals($responseListDocument['headers']['status-code'], 404);
$this->assertEquals($responseGetDocument['headers']['status-code'], 404);
}
$response = $this->client->call(Client::METHOD_PUT, '/database/collections/' . $data['moviesId'], array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'name' => 'Movies',
'enabled' => true,
'permission' => 'document',
]);
$this->assertEquals($response['headers']['status-code'], 200);
$this->assertTrue($response['body']['enabled']);
}
/**
* @depends testCreateCollection
*/