1
0
Fork 0
mirror of synced 2024-06-02 10:54:44 +12:00

Merge branch 'master' into fix-preview-when-no-extension

This commit is contained in:
Damodar Lohani 2022-03-18 16:23:42 +05:45 committed by GitHub
commit 43f60739d7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 276 additions and 95 deletions

View file

@ -5,14 +5,20 @@ tasks:
- init: docker-compose pull &&
docker-compose build &&
docker run --rm --interactive --tty --volume $PWD:/app composer update --ignore-platform-reqs --optimize-autoloader --no-plugins --no-scripts --prefer-dist
command: |
docker-compose up -d
ports:
- port: 8080
onOpen: ignore
onOpen: open-preview
visibility: public
vscode:
extensions:
- ms-azuretools.vscode-docker
github:
# https://www.gitpod.io/docs/prebuilds#github-specific-configuration
prebuilds:
# enable for pull requests coming from forks (defaults to false)
pullRequestsFromForks: true
# add a check to pull requests (defaults to true)
addCheck: false

View file

@ -1,3 +1,11 @@
# Version 0.13.3
## Bugs
- Fixed search for terms that inlcude `@` characters
- Fixed Bucket permissions
- Fixed file upload error in UI
- Fixed input field for float attributes in UI
- Fixed `appwrite-executor` restart behavior in docker-compose.yml
# Version 0.13.2
## Bugs
- Fixed global issue with write permissions

View file

@ -59,7 +59,7 @@ docker run -it --rm \
--volume /var/run/docker.sock:/var/run/docker.sock \
--volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
--entrypoint="install" \
appwrite/appwrite:0.13.2
appwrite/appwrite:0.13.3
```
### Windows
@ -71,7 +71,7 @@ docker run -it --rm ^
--volume //var/run/docker.sock:/var/run/docker.sock ^
--volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^
--entrypoint="install" ^
appwrite/appwrite:0.13.2
appwrite/appwrite:0.13.3
```
#### PowerShell
@ -81,7 +81,7 @@ docker run -it --rm ,
--volume /var/run/docker.sock:/var/run/docker.sock ,
--volume ${pwd}/appwrite:/usr/src/code/appwrite:rw ,
--entrypoint="install" ,
appwrite/appwrite:0.13.2
appwrite/appwrite:0.13.3
```
运行后,可以在浏览器上访问 http://localhost 找到 Appwrite 控制台。在非 Linux 的本机主机上完成安装后,服务器可能需要几分钟才能启动。

View file

@ -53,7 +53,7 @@ Table of Contents:
Appwrite backend server is designed to run in a container environment. Running your server is as easy as running one command from your terminal. You can either run Appwrite on your localhost using docker-compose or on any other container orchestration tool like Kubernetes, Docker Swarm, or Rancher.
The easiest way to start running your Appwrite server is by running our docker-compose file. Before running the installation command make sure you have [Docker](https://www.docker.com/products/docker-desktop) installed on your machine:
The easiest way to start running your Appwrite server is by running our docker-compose file. Before running the installation command, make sure you have [Docker](https://www.docker.com/products/docker-desktop) installed on your machine:
### Unix
@ -62,7 +62,7 @@ docker run -it --rm \
--volume /var/run/docker.sock:/var/run/docker.sock \
--volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
--entrypoint="install" \
appwrite/appwrite:0.13.2
appwrite/appwrite:0.13.3
```
### Windows
@ -74,7 +74,7 @@ docker run -it --rm ^
--volume //var/run/docker.sock:/var/run/docker.sock ^
--volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^
--entrypoint="install" ^
appwrite/appwrite:0.13.2
appwrite/appwrite:0.13.3
```
#### PowerShell
@ -84,7 +84,7 @@ docker run -it --rm ,
--volume /var/run/docker.sock:/var/run/docker.sock ,
--volume ${pwd}/appwrite:/usr/src/code/appwrite:rw ,
--entrypoint="install" ,
appwrite/appwrite:0.13.2
appwrite/appwrite:0.13.3
```
Once the Docker installation completes, go to http://localhost to access the Appwrite console from your browser. Please note that on non-Linux native hosts, the server might take a few minutes to start after installation completes.

View file

@ -1687,7 +1687,7 @@ App::get('/v1/database/collections/:collectionId/documents')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_DOCUMENT_LIST)
->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('queries', [], new ArrayList(new Text(0), 100), 'Array of query strings.', true)
->param('queries', [], new ArrayList(new Text(0), 100), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/database#querying-documents).', true)
->param('limit', 25, new Range(0, 100), 'Maximum number of documents to return in response. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true)
->param('offset', 0, new Range(0, APP_LIMIT_COUNT), 'Offset value. The default value is 0. Use this value to manage pagination. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
->param('cursor', '', new UID(), 'ID of the document used as the starting point for the query, excluding the document itself. Should be used for efficient pagination when working with large sets of data. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
@ -1949,7 +1949,7 @@ App::patch('/v1/database/collections/:collectionId/documents/:documentId')
->label('sdk.response.model', Response::MODEL_DOCUMENT)
->param('collectionId', null, new UID(), 'Collection ID.')
->param('documentId', null, new UID(), 'Document ID.')
->param('data', [], new JSON(), 'Document data as JSON object.')
->param('data', [], new JSON(), 'Document data as JSON object. Include only attribute and value pairs to be updated.')
->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)
->inject('response')

View file

@ -715,7 +715,7 @@ App::get('/v1/storage/buckets/:bucketId/files')
/** @var Appwrite\Stats\Stats $usage */
/** @var string $mode */
$bucket = $dbForProject->getDocument('buckets', $bucketId);
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
if ($bucket->isEmpty()
|| (!$bucket->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)) {
@ -738,9 +738,7 @@ App::get('/v1/storage/buckets/:bucketId/files')
if (!empty($cursor)) {
if ($bucket->getAttribute('permission') === 'bucket') {
$cursorFile = Authorization::skip(function () use ($dbForProject, $bucket, $cursor) {
return $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $cursor);
});
$cursorFile = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $cursor));
} else {
$cursorFile = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $cursor);
}
@ -757,9 +755,7 @@ App::get('/v1/storage/buckets/:bucketId/files')
}
if ($bucket->getAttribute('permission') === 'bucket') {
$files = Authorization::skip(function () use ($dbForProject, $bucket, $queries, $limit, $offset, $cursor, $cursorDirection, $orderType) {
return $dbForProject->find('bucket_' . $bucket->getInternalId(), $queries, $limit, $offset, [], [$orderType], $cursorFile ?? null, $cursorDirection);
});
$files = Authorization::skip(fn () => $dbForProject->find('bucket_' . $bucket->getInternalId(), $queries, $limit, $offset, [], [$orderType], $cursorFile ?? null, $cursorDirection));
} else {
$files = $dbForProject->find('bucket_' . $bucket->getInternalId(), $queries, $limit, $offset, [], [$orderType], $cursorFile ?? null, $cursorDirection);
}
@ -799,7 +795,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId')
/** @var Appwrite\Stats\Stats $usage */
/** @var string $mode */
$bucket = $dbForProject->getDocument('buckets', $bucketId);
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
if ($bucket->isEmpty()
|| (!$bucket->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)) {
@ -815,9 +811,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId')
}
if ($bucket->getAttribute('permission') === 'bucket') {
$file = Authorization::skip(function () use ($dbForProject, $bucket, $fileId) {
return $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
});
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
} else {
$file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
}
@ -825,10 +819,12 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId')
if ($file->isEmpty() || $file->getAttribute('bucketId') !== $bucketId) {
throw new Exception('File not found', 404, Exception::STORAGE_FILE_NOT_FOUND);
}
$usage
->setParam('storage.files.read', 1)
->setParam('bucketId', $bucketId)
;
$response->dynamic($file, Response::MODEL_FILE);
});
@ -879,7 +875,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
throw new Exception('Imagick extension is missing', 500, Exception::GENERAL_SERVER_ERROR);
}
$bucket = $dbForProject->getDocument('buckets', $bucketId);
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
if ($bucket->isEmpty()
|| (!$bucket->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)) {
@ -907,9 +903,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
if ($bucket->getAttribute('permission') === 'bucket') {
// skip authorization
$file = Authorization::skip(function () use ($dbForProject, $bucket, $fileId) {
return $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
});
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
} else {
$file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
}
@ -1048,7 +1042,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download')
/** @var Utopia\Storage\Device $deviceFiles */
/** @var string $mode */
$bucket = $dbForProject->getDocument('buckets', $bucketId);
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
if ($bucket->isEmpty()
|| (!$bucket->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)) {
@ -1064,9 +1058,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download')
}
if ($bucket->getAttribute('permission') === 'bucket') {
$file = Authorization::skip(function () use ($dbForProject, $fileId, $bucket) {
return $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
});
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
} else {
$file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
}
@ -1193,7 +1185,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view')
/** @var Utopia\Storage\Device $deviceFiles */
/** @var string $mode */
$bucket = $dbForProject->getDocument('buckets', $bucketId);
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
if ($bucket->isEmpty()
|| (!$bucket->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)) {
@ -1209,9 +1201,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view')
}
if ($bucket->getAttribute('permission') === 'bucket') {
$file = Authorization::skip(function () use ($dbForProject, $fileId, $bucket) {
return $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
});
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
} else {
$file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
}
@ -1356,7 +1346,7 @@ App::put('/v1/storage/buckets/:bucketId/files/:fileId')
/** @var Appwrite\Event\Event $events */
/** @var string $mode */
$bucket = $dbForProject->getDocument('buckets', $bucketId);
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
$read = (is_null($read) && !$user->isEmpty()) ? ['user:' . $user->getId()] : $read ?? []; // By default set read permissions for user
$write = (is_null($write) && !$user->isEmpty()) ? ['user:' . $user->getId()] : $write ?? [];
@ -1390,9 +1380,7 @@ App::put('/v1/storage/buckets/:bucketId/files/:fileId')
}
if ($bucket->getAttribute('permission') === 'bucket') {
$file = Authorization::skip(function () use ($dbForProject, $fileId, $bucket) {
return $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
});
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
} else {
$file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
}
@ -1401,18 +1389,15 @@ App::put('/v1/storage/buckets/:bucketId/files/:fileId')
throw new Exception('File not found', 404, Exception::STORAGE_FILE_NOT_FOUND);
}
$file
->setAttribute('$read', $read)
->setAttribute('$write', $write)
;
if ($bucket->getAttribute('permission') === 'bucket') {
$file = Authorization::skip(function () use ($dbForProject, $fileId, $bucket, $file, $read, $write) {
return $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file
->setAttribute('$read', $read)
->setAttribute('$write', $write)
);
});
$file = Authorization::skip(fn () => $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file));
} else {
$file = $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file
->setAttribute('$read', $read)
->setAttribute('$write', $write)
);
$file = $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file);
}
$events->setParam('bucket', $bucket->getArrayCopy());
@ -1462,7 +1447,7 @@ App::delete('/v1/storage/buckets/:bucketId/files/:fileId')
/** @var Utopia\Storage\Device $deviceFiles */
/** @var string $mode */
$bucket = $dbForProject->getDocument('buckets', $bucketId);
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
if ($bucket->isEmpty()
|| (!$bucket->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)) {
@ -1478,9 +1463,7 @@ App::delete('/v1/storage/buckets/:bucketId/files/:fileId')
}
if ($bucket->getAttribute('permission') === 'bucket') {
$file = Authorization::skip(function () use ($dbForProject, $fileId, $bucket) {
return $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
});
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
} else {
$file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
}
@ -1506,9 +1489,7 @@ App::delete('/v1/storage/buckets/:bucketId/files/:fileId')
$deviceLocal->delete($cacheDir, true);
if ($bucket->getAttribute('permission') === 'bucket') {
$deleted = Authorization::skip(function () use ($dbForProject, $fileId, $bucket) {
return $dbForProject->deleteDocument('bucket_' . $bucket->getInternalId(), $fileId);
});
$deleted = Authorization::skip(fn () => $dbForProject->deleteDocument('bucket_' . $bucket->getInternalId(), $fileId));
} else {
$deleted = $dbForProject->deleteDocument('bucket_' . $bucket->getInternalId(), $fileId);
}

View file

@ -69,8 +69,8 @@ const APP_LIMIT_USERS = 10000;
const APP_LIMIT_ANTIVIRUS = 20000000; //20MB
const APP_LIMIT_ENCRYPTION = 20000000; //20MB
const APP_LIMIT_COMPRESSION = 20000000; //20MB
const APP_CACHE_BUSTER = 302;
const APP_VERSION_STABLE = '0.13.2';
const APP_CACHE_BUSTER = 303;
const APP_VERSION_STABLE = '0.13.3';
const APP_DATABASE_ATTRIBUTE_EMAIL = 'email';
const APP_DATABASE_ATTRIBUTE_ENUM = 'enum';
const APP_DATABASE_ATTRIBUTE_IP = 'ip';

View file

@ -762,21 +762,21 @@ $logs = $this->getParam('logs', null);
<div class="row responsive thin">
<div class="col span-6 margin-bottom-small">
<label for="float-min">Min</label>
<input id="float-min" type="number" class="full-width" name="min" step="0.01" autocomplete="off" data-cast-to="float" />
<input id="float-min" type="number" class="full-width" name="min" step="any" autocomplete="off" data-cast-to="float" />
</div>
<div class="col span-6 margin-bottom-small">
<label for="float-max">Max</label>
<input id="float-max" type="number" class="full-width" name="max" step="0.01" autocomplete="off" data-cast-to="float" />
<input id="float-max" type="number" class="full-width" name="max" step="any" autocomplete="off" data-cast-to="float" />
</div>
</div>
<label for="float-default">Default Value</label>
<template x-if="!(array || required)">
<input name="xdefault" type="number" step="0.01" class="margin-bottom-large" autocomplete="off" data-cast-to="float">
<input name="xdefault" type="number" step="any" class="margin-bottom-large" autocomplete="off" data-cast-to="float">
</template>
<template x-if="(array || required)">
<input name="xdefault" type="number" step="0.01" class="margin-bottom-large" autocomplete="off" data-cast-to="float" disabled>
<input name="xdefault" type="number" step="any" class="margin-bottom-large" autocomplete="off" data-cast-to="float" disabled>
</template>
<footer>

View file

@ -110,7 +110,7 @@ $logs = $this->getParam('logs', null);
<template x-if="attr.type === 'double'">
<input
type="number"
step="0.01"
step="any"
:placeholder="attr.default"
:name="attr.key"
:required="attr.required"
@ -203,7 +203,7 @@ $logs = $this->getParam('logs', null);
<template x-if="attr.type === 'double'">
<input
type="number"
step="0.01"
step="any"
:placeholder="attr.default"
:name="attr.key"
:required="attr.required"

View file

@ -173,6 +173,7 @@ services:
image: <?php echo $organization; ?>/<?php echo $image; ?>:<?php echo $version."\n"; ?>
entrypoint: executor
container_name: appwrite-executor
restart: unless-stopped
stop_signal: SIGINT
networks:
appwrite:
@ -561,4 +562,4 @@ volumes:
appwrite-functions:
appwrite-influxdb:
appwrite-config:
appwrite-executor:
appwrite-executor:

37
composer.lock generated
View file

@ -236,16 +236,16 @@
},
{
"name": "chillerlan/php-settings-container",
"version": "2.1.2",
"version": "2.1.3",
"source": {
"type": "git",
"url": "https://github.com/chillerlan/php-settings-container.git",
"reference": "ec834493a88682dd69652a1eeaf462789ed0c5f5"
"reference": "125dd573b45ffc7cabecf385986a356ba2c6f602"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/chillerlan/php-settings-container/zipball/ec834493a88682dd69652a1eeaf462789ed0c5f5",
"reference": "ec834493a88682dd69652a1eeaf462789ed0c5f5",
"url": "https://api.github.com/repos/chillerlan/php-settings-container/zipball/125dd573b45ffc7cabecf385986a356ba2c6f602",
"reference": "125dd573b45ffc7cabecf385986a356ba2c6f602",
"shasum": ""
},
"require": {
@ -253,7 +253,7 @@
"php": "^7.4 || ^8.0"
},
"require-dev": {
"phan/phan": "^4.0",
"phan/phan": "^5.3",
"phpunit/phpunit": "^9.5"
},
"type": "library",
@ -278,6 +278,7 @@
"keywords": [
"PHP7",
"Settings",
"configuration",
"container",
"helper"
],
@ -295,7 +296,7 @@
"type": "ko_fi"
}
],
"time": "2021-09-06T15:17:01+00:00"
"time": "2022-03-09T13:18:58+00:00"
},
{
"name": "colinmollenhour/credis",
@ -2129,16 +2130,16 @@
},
{
"name": "utopia-php/database",
"version": "0.15.3",
"version": "0.15.4",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/database.git",
"reference": "726e3b04fb1f1b3b654bc1d3114deaf1481cb008"
"reference": "166365d9c39c4d70b1267af4562f4327e8930f79"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/database/zipball/726e3b04fb1f1b3b654bc1d3114deaf1481cb008",
"reference": "726e3b04fb1f1b3b654bc1d3114deaf1481cb008",
"url": "https://api.github.com/repos/utopia-php/database/zipball/166365d9c39c4d70b1267af4562f4327e8930f79",
"reference": "166365d9c39c4d70b1267af4562f4327e8930f79",
"shasum": ""
},
"require": {
@ -2186,9 +2187,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/database/issues",
"source": "https://github.com/utopia-php/database/tree/0.15.3"
"source": "https://github.com/utopia-php/database/tree/0.15.4"
},
"time": "2022-03-07T11:59:51+00:00"
"time": "2022-03-15T17:20:14+00:00"
},
{
"name": "utopia-php/domains",
@ -3191,16 +3192,16 @@
},
{
"name": "composer/semver",
"version": "3.2.9",
"version": "3.3.0",
"source": {
"type": "git",
"url": "https://github.com/composer/semver.git",
"reference": "a951f614bd64dcd26137bc9b7b2637ddcfc57649"
"reference": "f79c90ad4e9b41ac4dfc5d77bf398cf61fbd718b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/composer/semver/zipball/a951f614bd64dcd26137bc9b7b2637ddcfc57649",
"reference": "a951f614bd64dcd26137bc9b7b2637ddcfc57649",
"url": "https://api.github.com/repos/composer/semver/zipball/f79c90ad4e9b41ac4dfc5d77bf398cf61fbd718b",
"reference": "f79c90ad4e9b41ac4dfc5d77bf398cf61fbd718b",
"shasum": ""
},
"require": {
@ -3252,7 +3253,7 @@
"support": {
"irc": "irc://irc.freenode.org/composer",
"issues": "https://github.com/composer/semver/issues",
"source": "https://github.com/composer/semver/tree/3.2.9"
"source": "https://github.com/composer/semver/tree/3.3.0"
},
"funding": [
{
@ -3268,7 +3269,7 @@
"type": "tidelift"
}
],
"time": "2022-02-04T13:58:43+00:00"
"time": "2022-03-15T08:35:57+00:00"
},
{
"name": "composer/xdebug-handler",

View file

@ -1 +1 @@
Delete a document by its unique ID. This endpoint deletes only the parent documents, its attributes and relations to other documents. Child documents **will not** be deleted.
Delete a document by its unique ID.

View file

@ -1,6 +1,6 @@
The Database service allows you to create structured collections of documents, query and filter lists of documents, and manage an advanced set of read and write access permissions.
All the data in the database service is stored in structured JSON documents. The Appwrite database service also allows you to nest child documents in parent documents and use deep filters to both search and query your data.
All the data in the database service is stored in structured JSON documents.
Each database document structure in your project is defined using the Appwrite [collection attributes](/docs/database#attributes). The collection attributes help you ensure all your user-submitted data is validated and stored according to the collection structure.

View file

@ -3672,7 +3672,7 @@ if(!match){return fail}
for(i=0,len=match.length;i<len;i++){if(!process(match[i])){return fail}}
return(date.getTime()/1000)}
return{format:format,strtotime:strtotime}}(),true);})(window);(function(window){"use strict";window.ls.container.set('env',function(){return APP_ENV;},true);})(window);(function(window){"use strict";window.ls.container.set('form',function(){function cast(value,to){if(value&&Array.isArray(value)&&to!=='array'){value=value.map(element=>cast(element,to));return value;}
switch(to){case'int':case'integer':value=parseInt(value);break;case'float':value=parseFloat(parseFloat(value).toFixed(2));break;case'numeric':value=Number(value);break;case'float':value=parseFloat(value);break;case'string':value=value.toString();if(value.length===0){value=null;}
switch(to){case'int':case'integer':value=parseInt(value);break;case'numeric':value=Number(value);break;case'float':value=parseFloat(value);break;case'string':value=value.toString();if(value.length===0){value=null;}
break;case'json':value=(value)?JSON.parse(value):[];break;case'array':value=(value&&value.constructor&&value.constructor===Array)?value:[value];break;case'array-empty':value=[];break;case'bool':case'boolean':value=(value==='false')?false:value;value=!!value;break;}
return value;}
function toJson(element,json){json=json||{};let name=element.getAttribute('name');let type=element.getAttribute('type');let castTo=element.getAttribute('data-cast-to');let ref=json;if(name&&'FORM'!==element.tagName){if(name.startsWith('[')){let splitName=name.split('.');if(splitName.length>1&&splitName[0].endsWith(']')){name=splitName[splitName.length-1];}}

View file

@ -622,7 +622,7 @@ if(!match){return fail}
for(i=0,len=match.length;i<len;i++){if(!process(match[i])){return fail}}
return(date.getTime()/1000)}
return{format:format,strtotime:strtotime}}(),true);})(window);(function(window){"use strict";window.ls.container.set('env',function(){return APP_ENV;},true);})(window);(function(window){"use strict";window.ls.container.set('form',function(){function cast(value,to){if(value&&Array.isArray(value)&&to!=='array'){value=value.map(element=>cast(element,to));return value;}
switch(to){case'int':case'integer':value=parseInt(value);break;case'float':value=parseFloat(parseFloat(value).toFixed(2));break;case'numeric':value=Number(value);break;case'float':value=parseFloat(value);break;case'string':value=value.toString();if(value.length===0){value=null;}
switch(to){case'int':case'integer':value=parseInt(value);break;case'numeric':value=Number(value);break;case'float':value=parseFloat(value);break;case'string':value=value.toString();if(value.length===0){value=null;}
break;case'json':value=(value)?JSON.parse(value):[];break;case'array':value=(value&&value.constructor&&value.constructor===Array)?value:[value];break;case'array-empty':value=[];break;case'bool':case'boolean':value=(value==='false')?false:value;value=!!value;break;}
return value;}
function toJson(element,json){json=json||{};let name=element.getAttribute('name');let type=element.getAttribute('type');let castTo=element.getAttribute('data-cast-to');let ref=json;if(name&&'FORM'!==element.tagName){if(name.startsWith('[')){let splitName=name.split('.');if(splitName.length>1&&splitName[0].endsWith(']')){name=splitName[splitName.length-1];}}

View file

@ -13,9 +13,6 @@
case 'integer':
value = parseInt(value);
break;
case 'float':
value = parseFloat(parseFloat(value).toFixed(2));
break;
case 'numeric':
value = Number(value);
break;

View file

@ -38,6 +38,7 @@ abstract class Migration
'0.13.0' => 'V12',
'0.13.1' => 'V12',
'0.13.2' => 'V12',
'0.13.3' => 'V12',
];
/**

View file

@ -12,6 +12,18 @@ class AttributeBoolean extends Attribute
parent::__construct();
$this
->addRule('key', [
'type' => self::TYPE_STRING,
'description' => 'Attribute Key.',
'default' => '',
'example' => 'isEnabled',
])
->addRule('type', [
'type' => self::TYPE_STRING,
'description' => 'Attribute type.',
'default' => '',
'example' => 'boolean',
])
->addRule('default', [
'type' => self::TYPE_BOOLEAN,
'description' => 'Default value for attribute when not provided. Cannot be set when attribute is required.',

View file

@ -12,6 +12,18 @@ class AttributeEmail extends Attribute
parent::__construct();
$this
->addRule('key', [
'type' => self::TYPE_STRING,
'description' => 'Attribute Key.',
'default' => '',
'example' => 'userEmail',
])
->addRule('type', [
'type' => self::TYPE_STRING,
'description' => 'Attribute type.',
'default' => '',
'example' => 'string',
])
->addRule('format', [
'type' => self::TYPE_STRING,
'description' => 'String format.',

View file

@ -12,6 +12,18 @@ class AttributeEnum extends Attribute
parent::__construct();
$this
->addRule('key', [
'type' => self::TYPE_STRING,
'description' => 'Attribute Key.',
'default' => '',
'example' => 'status',
])
->addRule('type', [
'type' => self::TYPE_STRING,
'description' => 'Attribute type.',
'default' => '',
'example' => 'string',
])
->addRule('elements', [
'type' => self::TYPE_STRING,
'description' => 'Array of elements in enumerated type.',

View file

@ -12,6 +12,18 @@ class AttributeFloat extends Attribute
parent::__construct();
$this
->addRule('key', [
'type' => self::TYPE_STRING,
'description' => 'Attribute Key.',
'default' => '',
'example' => 'percentageCompleted',
])
->addRule('type', [
'type' => self::TYPE_STRING,
'description' => 'Attribute type.',
'default' => '',
'example' => 'double',
])
->addRule('min', [
'type' => self::TYPE_FLOAT,
'description' => 'Minimum value to enforce for new documents.',

View file

@ -12,6 +12,18 @@ class AttributeIP extends Attribute
parent::__construct();
$this
->addRule('key', [
'type' => self::TYPE_STRING,
'description' => 'Attribute Key.',
'default' => '',
'example' => 'ipAddress',
])
->addRule('type', [
'type' => self::TYPE_STRING,
'description' => 'Attribute type.',
'default' => '',
'example' => 'string',
])
->addRule('format', [
'type' => self::TYPE_STRING,
'description' => 'String format.',

View file

@ -12,6 +12,18 @@ class AttributeInteger extends Attribute
parent::__construct();
$this
->addRule('key', [
'type' => self::TYPE_STRING,
'description' => 'Attribute Key.',
'default' => '',
'example' => 'count',
])
->addRule('type', [
'type' => self::TYPE_STRING,
'description' => 'Attribute type.',
'default' => '',
'example' => 'integer',
])
->addRule('min', [
'type' => self::TYPE_INTEGER,
'description' => 'Minimum value to enforce for new documents.',

View file

@ -12,6 +12,18 @@ class AttributeURL extends Attribute
parent::__construct();
$this
->addRule('key', [
'type' => self::TYPE_STRING,
'description' => 'Attribute Key.',
'default' => '',
'example' => 'githubUrl',
])
->addRule('type', [
'type' => self::TYPE_STRING,
'description' => 'Attribute type.',
'default' => '',
'example' => 'string',
])
->addRule('format', [
'type' => self::TYPE_STRING,
'description' => 'String format.',

View file

@ -65,7 +65,7 @@ trait StorageBase
]);
$this->assertEquals(201, $bucket2['headers']['status-code']);
$this->assertNotEmpty($bucket2['body']['$id']);
/**
* Chunked Upload
*/
@ -99,7 +99,7 @@ trait StorageBase
$id = $largeFile['body']['$id'];
}
@fclose($handle);
$this->assertEquals(201, $largeFile['headers']['status-code']);
$this->assertNotEmpty($largeFile['body']['$id']);
$this->assertIsInt($largeFile['body']['dateCreated']);
@ -107,7 +107,7 @@ trait StorageBase
$this->assertEquals('video/mp4', $largeFile['body']['mimeType']);
$this->assertEquals($totalSize, $largeFile['body']['sizeOriginal']);
$this->assertEquals(md5_file(realpath(__DIR__ . '/../../../resources/disk-a/large-file.mp4')), $largeFile['body']['signature']); // should validate that the file is not encrypted
/**
* Failure
* Test for Chunk above 5MB
@ -134,9 +134,9 @@ trait StorageBase
'write' => ['role:all'],
]);
@fclose($handle);
$this->assertEquals(413, $res['headers']['status-code']);
/**
* Test for FAILURE unknown Bucket

View file

@ -17,6 +17,96 @@ class StorageCustomClientTest extends Scope
use ProjectCustom;
use SideClient;
public function testBucketPermissions(): void
{
/**
* Test for SUCCESS
*/
$bucket = $this->client->call(Client::METHOD_POST, '/storage/buckets', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
], [
'bucketId' => 'unique()',
'name' => 'Test Bucket',
'permission' => 'bucket',
'read' => ['role:all'],
'write' => ['role:member'],
]);
$bucketId = $bucket['body']['$id'];
$this->assertEquals(201, $bucket['headers']['status-code']);
$this->assertNotEmpty($bucketId);
$file = $this->client->call(Client::METHOD_POST, '/storage/buckets/'. $bucketId . '/files', array_merge([
'content-type' => 'multipart/form-data',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'fileId' => 'unique()',
'file' => new CURLFile(realpath(__DIR__ . '/../../../resources/logo.png'), 'image/png', 'permissions.png'),
]);
$fileId = $file['body']['$id'];
$this->assertEquals($file['headers']['status-code'], 201);
$this->assertNotEmpty($fileId);
$this->assertIsInt($file['body']['dateCreated']);
$this->assertEquals('permissions.png', $file['body']['name']);
$this->assertEquals('image/png', $file['body']['mimeType']);
$this->assertEquals(47218, $file['body']['sizeOriginal']);
$file = $this->client->call(Client::METHOD_GET, '/storage/buckets/' . $bucketId . '/files/' . $fileId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$this->assertEquals(200, $file['headers']['status-code']);
$file = $this->client->call(Client::METHOD_GET, '/storage/buckets/' . $bucketId . '/files/' . $fileId . '/preview', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$this->assertEquals(200, $file['headers']['status-code']);
$file = $this->client->call(Client::METHOD_GET, '/storage/buckets/' . $bucketId . '/files/' . $fileId . '/download', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$this->assertEquals(200, $file['headers']['status-code']);
$file = $this->client->call(Client::METHOD_GET, '/storage/buckets/' . $bucketId . '/files/' . $fileId . '/view', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$this->assertEquals(200, $file['headers']['status-code']);
/**
* Test for FAILURE
*/
$file = $this->client->call(Client::METHOD_POST, '/storage/buckets/'. $bucketId . '/files', [
'content-type' => 'multipart/form-data',
'x-appwrite-project' => $this->getProject()['$id'],
], [
'fileId' => 'unique()',
'file' => new CURLFile(realpath(__DIR__ . '/../../../resources/logo.png'), 'image/png', 'permissions.png'),
]);
$this->assertEquals($file['headers']['status-code'], 401);
/**
* Test for SUCCESS
*/
$file = $this->client->call(Client::METHOD_DELETE, '/storage/buckets/' . $bucketId . '/files/' . $fileId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$this->assertEquals(204, $file['headers']['status-code']);
$this->assertEmpty($file['body']);
}
public function testCreateFileDefaultPermissions(): array
{
/**

View file

@ -121,6 +121,18 @@ trait UsersBase
$this->assertCount(1, $response['body']['users']);
$this->assertEquals($response['body']['users'][0]['$id'], $data['userId']);
$response = $this->client->call(Client::METHOD_GET, '/users', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'search' => 'cristiano.ronaldo@manchester-united.co.uk'
]);
$this->assertEquals($response['headers']['status-code'], 200);
$this->assertNotEmpty($response['body']);
$this->assertNotEmpty($response['body']['users']);
$this->assertCount(1, $response['body']['users']);
$this->assertEquals($response['body']['users'][0]['$id'], $data['userId']);
$response = $this->client->call(Client::METHOD_GET, '/users', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],