1
0
Fork 0
mirror of synced 2024-06-26 18:20:43 +12:00

Merge remote-tracking branch 'origin/feat-database-indexing' into feat-db-search-attribute

This commit is contained in:
kodumbeats 2021-10-05 11:04:09 -04:00
commit f2c678bf41
108 changed files with 6229 additions and 1316 deletions

View file

@ -1,11 +1,15 @@
dist: xenial
dist: focal
arch:
- amd64
- arm64
- arm64-graviton2
os: linux
# Small change
vm:
size: large
language: shell
notifications:
@ -30,11 +34,17 @@ before_install:
- echo "_APP_FUNCTIONS_RUNTIMES=php-8.0" >> .env
install:
- docker-compose up -d
- docker-compose up -d --build
- sleep 10
script:
- docker ps -a
# Tests should fail if any container is in exited status
- ALL_UP=`docker ps -aq --filter "status=exited"`
- >
if [[ "$ALL_UP" != "" ]]; then
exit 1
fi
- docker-compose logs appwrite
- docker-compose logs mariadb
- docker-compose logs appwrite-worker-functions
@ -43,6 +53,9 @@ script:
- docker-compose exec appwrite test --debug
- docker-compose logs appwrite
after_failure:
- docker-compose logs appwrite
deploy:
- provider: script
edge: true

View file

@ -6,6 +6,12 @@
- Grouped oAuth related attributes in project collection. Introduced new attribute `providers` and removed all attributes related to OAuth2 providers. All OAuth2 attributes are grouped under `providers`
- Project model changed, `userAuth<AuthMethod>` => `auth<AuthMethod>` example `userAuthEmailPassword` => `authEmailPassword`, also `userOauth2<Provider>...` => `provider<Provider>...` example `userOauth2GithubAppid` => `providerGithubAppid`
# Version 0.9.4
## Security
- Fixed security vulnerability that exposes project ID's from other admin users (#1453)
# Version 0.9.3
## Bugs

View file

@ -19,8 +19,8 @@ ENV DEBUG=$DEBUG
ENV PHP_REDIS_VERSION=5.3.4 \
PHP_MONGODB_VERSION=1.9.1 \
PHP_SWOOLE_VERSION=v4.6.7 \
PHP_IMAGICK_VERSION=3.5.0 \
PHP_SWOOLE_VERSION=v4.7.0 \
PHP_IMAGICK_VERSION=3.5.1 \
PHP_YAML_VERSION=2.2.1 \
PHP_MAXMINDDB_VERSION=v1.10.1
@ -225,6 +225,7 @@ RUN mkdir -p /storage/uploads && \
# Executables
RUN chmod +x /usr/local/bin/doctor && \
chmod +x /usr/local/bin/maintenance && \
chmod +x /usr/local/bin/usage && \
chmod +x /usr/local/bin/install && \
chmod +x /usr/local/bin/migrate && \
chmod +x /usr/local/bin/schedule && \

View file

@ -57,7 +57,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.9.3
appwrite/appwrite:0.9.4
```
### Windows
@ -69,7 +69,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.9.3
appwrite/appwrite:0.9.4
```
#### PowerShell
@ -79,7 +79,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.9.3
appwrite/appwrite:0.9.4
```
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

@ -1,6 +1,6 @@
<?php
require_once __DIR__.'/workers.php';
require_once __DIR__.'/init.php';
use Utopia\App;
use Utopia\CLI\CLI;
@ -15,6 +15,7 @@ include 'tasks/migrate.php';
include 'tasks/sdks.php';
include 'tasks/ssl.php';
include 'tasks/vars.php';
include 'tasks/usage.php';
$cli
->task('version')

View file

@ -7,8 +7,333 @@ $providers = Config::getParam('providers', []);
$auth = Config::getParam('auth', []);
$collections = [
'collections' => [
'$collection' => Database::METADATA,
'$id' => 'collections',
'name' => 'Collections',
'attributes' => [
[
'$id' => 'name',
'type' => Database::VAR_STRING,
'size' => 256,
'required' => true,
'signed' => true,
'array' => false,
'filters' => [],
],
[
'$id' => 'dateCreated',
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => 0,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'dateUpdated',
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => 0,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'permission',
'type' => Database::VAR_STRING,
'size' => 64,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'attributes',
'type' => Database::VAR_STRING,
'size' => 1000000,
'required' => false,
'signed' => true,
'array' => false,
'filters' => ['subQueryAttributes'],
],
[
'$id' => 'indexes',
'type' => Database::VAR_STRING,
'size' => 1000000,
'required' => false,
'signed' => true,
'array' => false,
'filters' => ['subQueryIndexes'],
],
[
'$id' => 'search',
'type' => Database::VAR_STRING,
'format' => '',
'size' => 16384,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
],
'indexes' => [
[
'$id' => '_fulltext_search',
'type' => Database::INDEX_FULLTEXT,
'attributes' => ['search'],
'lengths' => [1024],
'orders' => [Database::ORDER_ASC],
],
],
],
'attributes' => [
'$collection' => Database::METADATA,
'$id' => 'attributes',
'name' => 'Attributes',
'attributes' => [
[
'$id' => 'collectionId',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'key',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'type',
'type' => Database::VAR_STRING,
'format' => '',
'size' => 256,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'status',
'type' => Database::VAR_STRING,
'format' => '',
'size' => 16,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'size',
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => 0,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'required',
'type' => Database::VAR_BOOLEAN,
'format' => '',
'size' => 0,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'default',
'type' => Database::VAR_STRING,
'format' => '',
'size' => 16384,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => ['casting'],
],
[
'$id' => 'signed',
'type' => Database::VAR_BOOLEAN,
'size' => 0,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'array',
'type' => Database::VAR_BOOLEAN,
'size' => 0,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'format',
'type' => Database::VAR_STRING,
'size' => 64,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'formatOptions',
'type' => Database::VAR_STRING,
'size' => 16384,
'signed' => true,
'required' => false,
'default' => new stdClass,
'array' => false,
'filters' => ['json', 'range'],
],
[
'$id' => 'filters',
'type' => Database::VAR_STRING,
'size' => 64,
'signed' => true,
'required' => false,
'default' => null,
'array' => true,
'filters' => [],
],
],
'indexes' => [
[
'$id' => '_key_collection',
'type' => Database::INDEX_KEY,
'attributes' => ['collectionId'],
'lengths' => [Database::LENGTH_KEY],
'orders' => [Database::ORDER_ASC],
],
],
],
'indexes' => [
'$collection' => Database::METADATA,
'$id' => 'indexes',
'name' => 'Indexes',
'attributes' => [
[
'$id' => 'collectionId',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'key',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'type',
'type' => Database::VAR_STRING,
'format' => '',
'size' => 16,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'status',
'type' => Database::VAR_STRING,
'format' => '',
'size' => 16,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'attributes',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => true,
'filters' => [],
],
[
'$id' => 'lengths',
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => 0,
'signed' => true,
'required' => false,
'default' => null,
'array' => true,
'filters' => [],
],
[
'$id' => 'orders',
'type' => Database::VAR_STRING,
'format' => '',
'size' => 4,
'signed' => true,
'required' => false,
'default' => null,
'array' => true,
'filters' => [],
],
],
'indexes' => [
[
'$id' => '_key_collection',
'type' => Database::INDEX_KEY,
'attributes' => ['collectionId'],
'lengths' => [Database::LENGTH_KEY],
'orders' => [Database::ORDER_ASC],
],
],
],
'projects' => [
'$collection' => Database::COLLECTIONS,
'$collection' => Database::METADATA,
'$id' => 'projects',
'name' => 'Projects',
'attributes' => [
@ -245,7 +570,7 @@ $collections = [
],
'users' => [
'$collection' => Database::COLLECTIONS,
'$collection' => Database::METADATA,
'$id' => 'users',
'name' => 'Users',
'attributes' => [
@ -412,7 +737,7 @@ $collections = [
],
'sessions' => [
'$collection' => Database::COLLECTIONS,
'$collection' => Database::METADATA,
'$id' => 'sessions',
'name' => 'Sessions',
'attributes' => [
@ -660,7 +985,7 @@ $collections = [
],
'teams' => [
'$collection' => Database::COLLECTIONS,
'$collection' => Database::METADATA,
'$id' => 'teams',
'name' => 'Teams',
'attributes' => [
@ -721,7 +1046,7 @@ $collections = [
],
'memberships' => [
'$collection' => Database::COLLECTIONS,
'$collection' => Database::METADATA,
'$id' => 'memberships',
'name' => 'Memberships',
'attributes' => [
@ -829,7 +1154,7 @@ $collections = [
],
'files' => [
'$collection' => Database::COLLECTIONS,
'$collection' => Database::METADATA,
'$id' => 'files',
'name' => 'Files',
'attributes' => [
@ -1019,7 +1344,7 @@ $collections = [
],
'functions' => [
'$collection' => Database::COLLECTIONS,
'$collection' => Database::METADATA,
'$id' => 'functions',
'name' => 'Functions',
'attributes' => [
@ -1191,7 +1516,7 @@ $collections = [
],
'tags' => [
'$collection' => Database::COLLECTIONS,
'$collection' => Database::METADATA,
'$id' => 'tags',
'name' => 'Tags',
'attributes' => [
@ -1282,7 +1607,7 @@ $collections = [
],
'executions' => [
'$collection' => Database::COLLECTIONS,
'$collection' => Database::METADATA,
'$id' => 'executions',
'name' => 'Executions',
'attributes' => [
@ -1399,7 +1724,7 @@ $collections = [
],
'certificates' => [
'$collection' => Database::COLLECTIONS,
'$collection' => Database::METADATA,
'$id' => 'certificates',
'name' => 'Certificates',
'attributes' => [
@ -1483,6 +1808,91 @@ $collections = [
],
],
],
'stats' => [
'$collection' => Database::METADATA,
'$id' => 'stats',
'name' => 'Stats',
'attributes' => [
[
'$id' => 'metric',
'type' => Database::VAR_STRING,
'format' => '',
'size' => 255,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'value',
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => 0,
'signed' => false,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'time',
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => 0,
'signed' => false,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'period',
'type' => Database::VAR_STRING,
'format' => '',
'size' => 4,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'type',
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => 1,
'signed' => false,
'required' => true,
'default' => 0, // 0 -> count, 1 -> sum
'array' => false,
'filters' => [],
],
],
'indexes' => [
[
'$id' => '_key_time',
'type' => Database::INDEX_KEY,
'attributes' => ['time'],
'lengths' => [],
'orders' => [Database::ORDER_DESC],
],
[
'$id' => '_key_metric',
'type' => Database::INDEX_KEY,
'attributes' => ['metric'],
'lengths' => [],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => '_key_metric_period',
'type' => Database::INDEX_KEY,
'attributes' => ['metric', 'period'],
'lengths' => [],
'orders' => [Database::ORDER_DESC],
],
],
]
];
return $collections;

View file

@ -27,6 +27,21 @@ return [
'model' => Response::MODEL_USER,
'note' => '',
],
'users.update.email' => [
'description' => 'This event triggers when the user email address is updated.',
'model' => Response::MODEL_USER,
'note' => '',
],
'users.update.name' => [
'description' => 'This event triggers when the user name is updated.',
'model' => Response::MODEL_USER,
'note' => '',
],
'users.update.password' => [
'description' => 'This event triggers when the user password is updated.',
'model' => Response::MODEL_USER,
'note' => '',
],
'account.update.prefs' => [
'description' => 'This event triggers when the account preferences are updated.',
'model' => Response::MODEL_USER,

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -149,6 +149,15 @@ return [
'required' => false,
'question' => '',
'filter' => ''
],
[
'name' => '_APP_USAGE_AGGREGATION_INTERVAL',
'description' => 'Interval value containing the number of seconds that the Appwrite usage process should wait before aggregating stats and syncing it to mariadb from InfluxDB. The default value is 30 seconds.',
'introduction' => '0.10.0',
'default' => '30',
'required' => false,
'question' => '',
'filter' => ''
]
],
],

View file

@ -43,7 +43,7 @@ App::post('/v1/account')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_USER)
->label('abuse-limit', 10)
->param('userId', '', new CustomId(), 'Unique Id. Choose your own unique ID or pass the string `unique()` to auto generate it. Valid chars are a-z, A-Z, 0-9, and underscore. Can\'t start with a leading underscore. Max length is 36 chars.')
->param('userId', '', new CustomId(), 'Unique Id. Choose your own unique ID or pass the string `unique()` to auto generate it. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('email', '', new Email(), 'User email.')
->param('password', '', new Password(), 'User password. Must be between 6 to 32 chars.')
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
@ -52,12 +52,14 @@ App::post('/v1/account')
->inject('project')
->inject('dbForInternal')
->inject('audits')
->action(function ($userId, $email, $password, $name, $request, $response, $project, $dbForInternal, $audits) {
->inject('usage')
->action(function ($userId, $email, $password, $name, $request, $response, $project, $dbForInternal, $audits, $usage) {
/** @var Utopia\Swoole\Request $request */
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Document $project */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Appwrite\Event\Event $audits */
/** @var Appwrite\Stats\Stats $usage */
$email = \strtolower($email);
if ('console' === $project->getId()) {
@ -118,9 +120,12 @@ App::post('/v1/account')
$audits
->setParam('userId', $user->getId())
->setParam('event', 'account.create')
->setParam('resource', 'users/' . $user->getId())
->setParam('resource', 'user/' . $user->getId())
;
$usage
->setParam('users.create', 1)
;
$response->setStatusCode(Response::STATUS_CODE_CREATED);
$response->dynamic($user, Response::MODEL_USER);
});
@ -148,13 +153,15 @@ App::post('/v1/account/sessions')
->inject('locale')
->inject('geodb')
->inject('audits')
->action(function ($email, $password, $request, $response, $dbForInternal, $locale, $geodb, $audits) {
->inject('usage')
->action(function ($email, $password, $request, $response, $dbForInternal, $locale, $geodb, $audits, $usage) {
/** @var Utopia\Swoole\Request $request */
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Locale\Locale $locale */
/** @var MaxMind\Db\Reader $geodb */
/** @var Appwrite\Event\Event $audits */
/** @var Appwrite\Stats\Stats $usage */
$email = \strtolower($email);
$protocol = $request->getProtocol();
@ -165,7 +172,7 @@ App::post('/v1/account/sessions')
$audits
//->setParam('userId', $profile->getId())
->setParam('event', 'account.sessions.failed')
->setParam('resource', 'users/'.($profile ? $profile->getId() : ''))
->setParam('resource', 'user/'.($profile ? $profile->getId() : ''))
;
throw new Exception('Invalid credentials', 401); // Wrong password or username
@ -206,7 +213,7 @@ App::post('/v1/account/sessions')
$audits
->setParam('userId', $profile->getId())
->setParam('event', 'account.sessions.create')
->setParam('resource', 'users/' . $profile->getId())
->setParam('resource', 'user/' . $profile->getId())
;
if (!Config::getParam('domainVerification')) {
@ -228,6 +235,11 @@ App::post('/v1/account/sessions')
->setAttribute('countryName', $countryName)
;
$usage
->setParam('users.update', 1)
->setParam('users.sessions.create', 1)
->setParam('provider', 'email')
;
$response->dynamic($session, Response::MODEL_SESSION);
});
@ -358,7 +370,8 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
->inject('geodb')
->inject('audits')
->inject('events')
->action(function ($provider, $code, $state, $request, $response, $project, $user, $dbForInternal, $geodb, $audits, $events) use ($oauthDefaultSuccess) {
->inject('usage')
->action(function ($provider, $code, $state, $request, $response, $project, $user, $dbForInternal, $geodb, $audits, $events, $usage) use ($oauthDefaultSuccess) {
/** @var Utopia\Swoole\Request $request */
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Document $project */
@ -366,6 +379,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
/** @var Utopia\Database\Database $dbForInternal */
/** @var MaxMind\Db\Reader $geodb */
/** @var Appwrite\Event\Event $audits */
/** @var Appwrite\Stats\Stats $usage */
$protocol = $request->getProtocol();
$callback = $protocol . '://' . $request->getHostname() . '/v1/account/sessions/oauth2/callback/' . $provider . '/' . $project->getId();
@ -541,12 +555,17 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
$audits
->setParam('userId', $user->getId())
->setParam('event', 'account.sessions.create')
->setParam('resource', 'users/' . $user->getId())
->setParam('resource', 'user/' . $user->getId())
->setParam('data', ['provider' => $provider])
;
$events->setParam('eventData', $response->output($session, Response::MODEL_SESSION));
$usage
->setParam('users.sessions.create', 1)
->setParam('projectId', $project->getId())
->setParam('provider', 'oauth2-'.$provider)
;
if (!Config::getParam('domainVerification')) {
$response
->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($user->getId(), $secret)]))
@ -597,7 +616,8 @@ App::post('/v1/account/sessions/anonymous')
->inject('dbForInternal')
->inject('geodb')
->inject('audits')
->action(function ($request, $response, $locale, $user, $project, $dbForInternal, $geodb, $audits) {
->inject('usage')
->action(function ($request, $response, $locale, $user, $project, $dbForInternal, $geodb, $audits, $usage) {
/** @var Utopia\Swoole\Request $request */
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Locale\Locale $locale */
@ -606,6 +626,7 @@ App::post('/v1/account/sessions/anonymous')
/** @var Utopia\Database\Database $dbForInternal */
/** @var MaxMind\Db\Reader $geodb */
/** @var Appwrite\Event\Event $audits */
/** @var Appwrite\Stats\Stats $usage */
$protocol = $request->getProtocol();
@ -686,7 +707,12 @@ App::post('/v1/account/sessions/anonymous')
$audits
->setParam('userId', $user->getId())
->setParam('event', 'account.sessions.create')
->setParam('resource', 'users/' . $user->getId())
->setParam('resource', 'user/' . $user->getId())
;
$usage
->setParam('users.sessions.create', 1)
->setParam('provider', 'anonymous')
;
if (!Config::getParam('domainVerification')) {
@ -774,10 +800,15 @@ App::get('/v1/account')
->label('sdk.response.model', Response::MODEL_USER)
->inject('response')
->inject('user')
->action(function ($response, $user) {
->inject('usage')
->action(function ($response, $user, $usage) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Document $user */
/** @var Appwrite\Stats\Stats $usage */
$usage
->setParam('users.read', 1)
;
$response->dynamic($user, Response::MODEL_USER);
});
@ -794,12 +825,17 @@ App::get('/v1/account/prefs')
->label('sdk.response.model', Response::MODEL_PREFERENCES)
->inject('response')
->inject('user')
->action(function ($response, $user) {
->inject('usage')
->action(function ($response, $user, $usage) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Document $user */
/** @var Appwrite\Stats\Stats $usage */
$prefs = $user->getAttribute('prefs', new \stdClass());
$usage
->setParam('users.read', 1)
;
$response->dynamic(new Document($prefs), Response::MODEL_PREFERENCES);
});
@ -817,10 +853,12 @@ App::get('/v1/account/sessions')
->inject('response')
->inject('user')
->inject('locale')
->action(function ($response, $user, $locale) {
->inject('usage')
->action(function ($response, $user, $locale, $usage) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Document $user */
/** @var Utopia\Locale\Locale $locale */
/** @var Appwrite\Stats\Stats $usage */
$sessions = $user->getAttribute('sessions', []);
$current = Auth::sessionVerify($sessions, Auth::$secret);
@ -834,6 +872,9 @@ App::get('/v1/account/sessions')
$sessions[$key] = $session;
}
$usage
->setParam('users.read', 1)
;
$response->dynamic(new Document([
'sessions' => $sessions,
'sum' => count($sessions),
@ -856,13 +897,15 @@ App::get('/v1/account/logs')
->inject('locale')
->inject('geodb')
->inject('dbForInternal')
->action(function ($response, $user, $locale, $geodb, $dbForInternal) {
->inject('usage')
->action(function ($response, $user, $locale, $geodb, $dbForInternal, $usage) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Document $project */
/** @var Utopia\Database\Document $user */
/** @var Utopia\Locale\Locale $locale */
/** @var MaxMind\Db\Reader $geodb */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Appwrite\Stats\Stats $usage */
$audit = new Audit($dbForInternal);
@ -909,6 +952,9 @@ App::get('/v1/account/logs')
}
$usage
->setParam('users.read', 1)
;
$response->dynamic(new Document(['logs' => $output]), Response::MODEL_LOG_LIST);
});
@ -928,11 +974,13 @@ App::get('/v1/account/sessions/:sessionId')
->inject('user')
->inject('locale')
->inject('dbForInternal')
->action(function ($sessionId, $response, $user, $locale, $dbForInternal) {
->inject('usage')
->action(function ($sessionId, $response, $user, $locale, $dbForInternal, $usage) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Document $user */
/** @var Utopia\Locale\Locale $locale */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Appwrite\Stats\Stats $usage */
$sessions = $user->getAttribute('sessions', []);
$sessionId = ($sessionId === 'current')
@ -951,6 +999,10 @@ App::get('/v1/account/sessions/:sessionId')
->setAttribute('countryName', $countryName)
;
$usage
->setParam('users.read', 1)
;
return $response->dynamic($session, Response::MODEL_SESSION);
}
}
@ -975,11 +1027,13 @@ App::patch('/v1/account/name')
->inject('user')
->inject('dbForInternal')
->inject('audits')
->action(function ($name, $response, $user, $dbForInternal, $audits) {
->inject('usage')
->action(function ($name, $response, $user, $dbForInternal, $audits, $usage) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Document $user */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Appwrite\Event\Event $audits */
/** @var Appwrite\Stats\Stats $usage */
$user = $dbForInternal->updateDocument('users', $user->getId(), $user
->setAttribute('name', $name)
@ -989,7 +1043,11 @@ App::patch('/v1/account/name')
$audits
->setParam('userId', $user->getId())
->setParam('event', 'account.update.name')
->setParam('resource', 'users/' . $user->getId())
->setParam('resource', 'user/' . $user->getId())
;
$usage
->setParam('users.update', 1)
;
$response->dynamic($user, Response::MODEL_USER);
@ -1013,11 +1071,13 @@ App::patch('/v1/account/password')
->inject('user')
->inject('dbForInternal')
->inject('audits')
->action(function ($password, $oldPassword, $response, $user, $dbForInternal, $audits) {
->inject('usage')
->action(function ($password, $oldPassword, $response, $user, $dbForInternal, $audits, $usage) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Document $user */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Appwrite\Event\Event $audits */
/** @var Appwrite\Stats\Stats $usage */
// Check old password only if its an existing user.
if ($user->getAttribute('passwordUpdate') !== 0 && !Auth::passwordVerify($oldPassword, $user->getAttribute('password'))) { // Double check user password
@ -1032,9 +1092,12 @@ App::patch('/v1/account/password')
$audits
->setParam('userId', $user->getId())
->setParam('event', 'account.update.password')
->setParam('resource', 'users/' . $user->getId())
->setParam('resource', 'user/' . $user->getId())
;
$usage
->setParam('users.update', 1)
;
$response->dynamic($user, Response::MODEL_USER);
});
@ -1056,11 +1119,13 @@ App::patch('/v1/account/email')
->inject('user')
->inject('dbForInternal')
->inject('audits')
->action(function ($email, $password, $response, $user, $dbForInternal, $audits) {
->inject('usage')
->action(function ($email, $password, $response, $user, $dbForInternal, $audits, $usage) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Document $user */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Appwrite\Event\Event $audits */
/** @var Appwrite\Stats\Stats $usage */
$isAnonymousUser = is_null($user->getAttribute('email')) && is_null($user->getAttribute('password')); // Check if request is from an anonymous account for converting
@ -1072,25 +1137,32 @@ App::patch('/v1/account/email')
}
$email = \strtolower($email);
$profile = $dbForInternal->findOne('users', [new Query('email', Query::TYPE_EQUAL, [\strtolower($email)])]); // Get user by email address
$profile = $dbForInternal->findOne('users', [new Query('email', Query::TYPE_EQUAL, [$email])]); // Get user by email address
if ($profile) {
throw new Exception('User already registered', 400);
}
$user = $dbForInternal->updateDocument('users', $user->getId(), $user
->setAttribute('password', $isAnonymousUser ? Auth::passwordHash($password) : $user->getAttribute('password', ''))
->setAttribute('email', $email)
->setAttribute('emailVerification', false) // After this user needs to confirm mail again
->setAttribute('search', implode(' ', [$user->getId(), $user->getAttribute('name'), $user->getAttribute('email')]))
);
try {
$user = $dbForInternal->updateDocument('users', $user->getId(), $user
->setAttribute('password', $isAnonymousUser ? Auth::passwordHash($password) : $user->getAttribute('password', ''))
->setAttribute('email', $email)
->setAttribute('emailVerification', false) // After this user needs to confirm mail again
->setAttribute('search', implode(' ', [$user->getId(), $user->getAttribute('name'), $user->getAttribute('email')]))
);
} catch(Duplicate $th) {
throw new Exception('Email already exists', 409);
}
$audits
->setParam('userId', $user->getId())
->setParam('event', 'account.update.email')
->setParam('resource', 'users/' . $user->getId())
->setParam('resource', 'user/' . $user->getId())
;
$usage
->setParam('users.update', 1)
;
$response->dynamic($user, Response::MODEL_USER);
});
@ -1111,19 +1183,24 @@ App::patch('/v1/account/prefs')
->inject('user')
->inject('dbForInternal')
->inject('audits')
->action(function ($prefs, $response, $user, $dbForInternal, $audits) {
->inject('usage')
->action(function ($prefs, $response, $user, $dbForInternal, $audits, $usage) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Document $user */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Appwrite\Event\Event $audits */
/** @var Appwrite\Stats\Stats $usage */
$user = $dbForInternal->updateDocument('users', $user->getId(), $user->setAttribute('prefs', $prefs));
$audits
->setParam('event', 'account.update.prefs')
->setParam('resource', 'users/' . $user->getId())
->setParam('resource', 'user/' . $user->getId())
;
$usage
->setParam('users.update', 1)
;
$response->dynamic($user, Response::MODEL_USER);
});
@ -1144,13 +1221,15 @@ App::delete('/v1/account')
->inject('dbForInternal')
->inject('audits')
->inject('events')
->action(function ($request, $response, $user, $dbForInternal, $audits, $events) {
->inject('usage')
->action(function ($request, $response, $user, $dbForInternal, $audits, $events, $usage) {
/** @var Utopia\Swoole\Request $request */
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Document $user */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Appwrite\Event\Event $audits */
/** @var Appwrite\Event\Event $events */
/** @var Appwrite\Stats\Stats $usage */
$protocol = $request->getProtocol();
$user = $dbForInternal->updateDocument('users', $user->getId(), $user->setAttribute('status', false));
@ -1166,7 +1245,7 @@ App::delete('/v1/account')
$audits
->setParam('userId', $user->getId())
->setParam('event', 'account.delete')
->setParam('resource', 'users/' . $user->getId())
->setParam('resource', 'user/' . $user->getId())
->setParam('data', $user->getArrayCopy())
;
@ -1180,6 +1259,9 @@ App::delete('/v1/account')
;
}
$usage
->setParam('users.delete', 1)
;
$response
->addCookie(Auth::$cookieName . '_legacy', '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null)
->addCookie(Auth::$cookieName, '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite'))
@ -1207,7 +1289,8 @@ App::delete('/v1/account/sessions/:sessionId')
->inject('locale')
->inject('audits')
->inject('events')
->action(function ($sessionId, $request, $response, $user, $dbForInternal, $locale, $audits, $events) {
->inject('usage')
->action(function ($sessionId, $request, $response, $user, $dbForInternal, $locale, $audits, $events, $usage) {
/** @var Utopia\Swoole\Request $request */
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Document $user */
@ -1215,6 +1298,7 @@ App::delete('/v1/account/sessions/:sessionId')
/** @var Utopia\Locale\Locale $locale */
/** @var Appwrite\Event\Event $audits */
/** @var Appwrite\Event\Event $events */
/** @var Appwrite\Stats\Stats $usage */
$protocol = $request->getProtocol();
$sessionId = ($sessionId === 'current')
@ -1232,7 +1316,7 @@ App::delete('/v1/account/sessions/:sessionId')
$audits
->setParam('userId', $user->getId())
->setParam('event', 'account.sessions.delete')
->setParam('resource', '/user/' . $user->getId())
->setParam('resource', 'user/' . $user->getId())
;
$session->setAttribute('current', false);
@ -1261,6 +1345,10 @@ App::delete('/v1/account/sessions/:sessionId')
->setParam('eventData', $response->output($session, Response::MODEL_SESSION))
;
$usage
->setParam('users.sessions.delete', 1)
->setParam('users.update', 1)
;
return $response->noContent();
}
}
@ -1287,7 +1375,8 @@ App::delete('/v1/account/sessions')
->inject('locale')
->inject('audits')
->inject('events')
->action(function ($request, $response, $user, $dbForInternal, $locale, $audits, $events) {
->inject('usage')
->action(function ($request, $response, $user, $dbForInternal, $locale, $audits, $events, $usage) {
/** @var Utopia\Swoole\Request $request */
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Document $user */
@ -1295,6 +1384,7 @@ App::delete('/v1/account/sessions')
/** @var Utopia\Locale\Locale $locale */
/** @var Appwrite\Event\Event $audits */
/** @var Appwrite\Event\Event $events */
/** @var Appwrite\Stats\Stats $usage */
$protocol = $request->getProtocol();
$sessions = $user->getAttribute('sessions', []);
@ -1305,7 +1395,7 @@ App::delete('/v1/account/sessions')
$audits
->setParam('userId', $user->getId())
->setParam('event', 'account.sessions.delete')
->setParam('resource', '/user/' . $user->getId())
->setParam('resource', 'user/' . $user->getId())
;
if (!Config::getParam('domainVerification')) {
@ -1330,13 +1420,19 @@ App::delete('/v1/account/sessions')
$dbForInternal->updateDocument('users', $user->getId(), $user->setAttribute('sessions', []));
$numOfSessions = count($sessions);
$events
->setParam('eventData', $response->output(new Document([
'sessions' => $sessions,
'sum' => count($sessions),
'sum' => $numOfSessions,
]), Response::MODEL_SESSION_LIST))
;
$usage
->setParam('users.sessions.delete', $numOfSessions)
->setParam('users.update', 1)
;
$response->noContent();
});
@ -1364,7 +1460,8 @@ App::post('/v1/account/recovery')
->inject('mails')
->inject('audits')
->inject('events')
->action(function ($email, $url, $request, $response, $dbForInternal, $project, $locale, $mails, $audits, $events) {
->inject('usage')
->action(function ($email, $url, $request, $response, $dbForInternal, $project, $locale, $mails, $audits, $events, $usage) {
/** @var Utopia\Swoole\Request $request */
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
@ -1373,6 +1470,7 @@ App::post('/v1/account/recovery')
/** @var Appwrite\Event\Event $mails */
/** @var Appwrite\Event\Event $audits */
/** @var Appwrite\Event\Event $events */
/** @var Appwrite\Stats\Stats $usage */
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::$roles);
$isAppUser = Auth::isAppUser(Authorization::$roles);
@ -1437,9 +1535,12 @@ App::post('/v1/account/recovery')
$audits
->setParam('userId', $profile->getId())
->setParam('event', 'account.recovery.create')
->setParam('resource', 'users/' . $profile->getId())
->setParam('resource', 'user/' . $profile->getId())
;
$usage
->setParam('users.update', 1)
;
$response->setStatusCode(Response::STATUS_CODE_CREATED);
$response->dynamic($recovery, Response::MODEL_TOKEN);
});
@ -1465,10 +1566,12 @@ App::put('/v1/account/recovery')
->inject('response')
->inject('dbForInternal')
->inject('audits')
->action(function ($userId, $secret, $password, $passwordAgain, $response, $dbForInternal, $audits) {
->inject('usage')
->action(function ($userId, $secret, $password, $passwordAgain, $response, $dbForInternal, $audits, $usage) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Appwrite\Event\Event $audits */
/** @var Appwrite\Stats\Stats $usage */
if ($password !== $passwordAgain) {
throw new Exception('Passwords must match', 400);
@ -1511,9 +1614,12 @@ App::put('/v1/account/recovery')
$audits
->setParam('userId', $profile->getId())
->setParam('event', 'account.recovery.update')
->setParam('resource', 'users/' . $profile->getId())
->setParam('resource', 'user/' . $profile->getId())
;
$usage
->setParam('users.update', 1)
;
$response->dynamic($recovery, Response::MODEL_TOKEN);
});
@ -1541,7 +1647,8 @@ App::post('/v1/account/verification')
->inject('audits')
->inject('events')
->inject('mails')
->action(function ($url, $request, $response, $project, $user, $dbForInternal, $locale, $audits, $events, $mails) {
->inject('usage')
->action(function ($url, $request, $response, $project, $user, $dbForInternal, $locale, $audits, $events, $mails, $usage) {
/** @var Utopia\Swoole\Request $request */
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Document $project */
@ -1551,6 +1658,7 @@ App::post('/v1/account/verification')
/** @var Appwrite\Event\Event $audits */
/** @var Appwrite\Event\Event $events */
/** @var Appwrite\Event\Event $mails */
/** @var Appwrite\Stats\Stats $usage */
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::$roles);
$isAppUser = Auth::isAppUser(Authorization::$roles);
@ -1605,9 +1713,12 @@ App::post('/v1/account/verification')
$audits
->setParam('userId', $user->getId())
->setParam('event', 'account.verification.create')
->setParam('resource', 'users/' . $user->getId())
->setParam('resource', 'user/' . $user->getId())
;
$usage
->setParam('users.update', 1)
;
$response->setStatusCode(Response::STATUS_CODE_CREATED);
$response->dynamic($verification, Response::MODEL_TOKEN);
});
@ -1632,11 +1743,13 @@ App::put('/v1/account/verification')
->inject('user')
->inject('dbForInternal')
->inject('audits')
->action(function ($userId, $secret, $response, $user, $dbForInternal, $audits) {
->inject('usage')
->action(function ($userId, $secret, $response, $user, $dbForInternal, $audits, $usage) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Document $user */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Appwrite\Event\Event $audits */
/** @var Appwrite\Stats\Stats $usage */
$profile = $dbForInternal->getDocument('users', $userId);
@ -1671,8 +1784,11 @@ App::put('/v1/account/verification')
$audits
->setParam('userId', $profile->getId())
->setParam('event', 'account.verification.update')
->setParam('resource', 'users/' . $user->getId())
->setParam('resource', 'user/' . $user->getId())
;
$usage
->setParam('users.update', 1)
;
$response->dynamic($verification, Response::MODEL_TOKEN);
});

File diff suppressed because it is too large Load diff

View file

@ -39,7 +39,7 @@ App::post('/v1/functions')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_FUNCTION)
->param('functionId', '', new CustomId(), 'Unique Id. Choose your own unique ID or pass the string `unique()` to auto generate it. Valid chars are a-z, A-Z, 0-9, and underscore. Can\'t start with a leading underscore. Max length is 36 chars.')
->param('functionId', '', new CustomId(), 'Unique Id. Choose your own unique ID or pass the string `unique()` to auto generate it. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('name', '', new Text(128), 'Function name. Max length: 128 chars.')
->param('execute', [], new ArrayList(new Text(64)), 'An array of strings with execution permissions. By default no user is granted with any execute permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.')
->param('runtime', '', new WhiteList(array_keys(Config::getParam('runtimes')), true), 'Execution runtime.')
@ -152,6 +152,9 @@ App::get('/v1/functions/:functionId/usage')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'functions')
->label('sdk.method', 'getUsage')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_USAGE_FUNCTIONS)
->param('functionId', '', new UID(), 'Function unique ID.')
->param('range', '30d', new WhiteList(['24h', '7d', '30d', '90d']), 'Date range.', true)
->inject('response')
@ -170,99 +173,62 @@ App::get('/v1/functions/:functionId/usage')
throw new Exception('Function not found', 404);
}
$usage = [];
if(App::getEnv('_APP_USAGE_STATS', 'enabled') == 'enabled') {
$period = [
'24h' => [
'start' => DateTime::createFromFormat('U', \strtotime('-24 hours')),
'end' => DateTime::createFromFormat('U', \strtotime('+1 hour')),
'group' => '30m',
'period' => '30m',
'limit' => 48,
],
'7d' => [
'start' => DateTime::createFromFormat('U', \strtotime('-7 days')),
'end' => DateTime::createFromFormat('U', \strtotime('now')),
'group' => '1d',
'period' => '1d',
'limit' => 7,
],
'30d' => [
'start' => DateTime::createFromFormat('U', \strtotime('-30 days')),
'end' => DateTime::createFromFormat('U', \strtotime('now')),
'group' => '1d',
'period' => '1d',
'limit' => 30,
],
'90d' => [
'start' => DateTime::createFromFormat('U', \strtotime('-90 days')),
'end' => DateTime::createFromFormat('U', \strtotime('now')),
'group' => '1d',
'period' => '1d',
'limit' => 90,
],
];
$metrics = [
"functions.$functionId.executions",
"functions.$functionId.failures",
"functions.$functionId.compute"
];
$stats = [];
Authorization::skip(function() use ($dbForInternal, $period, $range, $metrics, &$stats) {
foreach ($metrics as $metric) {
$requestDocs = $dbForInternal->find('stats', [
new Query('period', Query::TYPE_EQUAL, [$period[$range]['period']]),
new Query('metric', Query::TYPE_EQUAL, [$metric]),
], $period[$range]['limit'], 0, ['time'], [Database::ORDER_DESC]);
$client = $register->get('influxdb');
$executions = [];
$failures = [];
$compute = [];
if ($client) {
$start = $period[$range]['start']->format(DateTime::RFC3339);
$end = $period[$range]['end']->format(DateTime::RFC3339);
$database = $client->selectDB('telegraf');
// Executions
$result = $database->query('SELECT sum(value) AS "value" FROM "appwrite_usage_executions_all" WHERE time > \''.$start.'\' AND time < \''.$end.'\' AND "metric_type"=\'counter\' AND "project"=\''.$project->getId().'\' AND "functionId"=\''.$function->getId().'\' GROUP BY time('.$period[$range]['group'].') FILL(null)');
$points = $result->getPoints();
foreach ($points as $point) {
$executions[] = [
'value' => (!empty($point['value'])) ? $point['value'] : 0,
'date' => \strtotime($point['time']),
];
}
// Failures
$result = $database->query('SELECT sum(value) AS "value" FROM "appwrite_usage_executions_all" WHERE time > \''.$start.'\' AND time < \''.$end.'\' AND "metric_type"=\'counter\' AND "project"=\''.$project->getId().'\' AND "functionId"=\''.$function->getId().'\' AND "functionStatus"=\'failed\' GROUP BY time('.$period[$range]['group'].') FILL(null)');
$points = $result->getPoints();
foreach ($points as $point) {
$failures[] = [
'value' => (!empty($point['value'])) ? $point['value'] : 0,
'date' => \strtotime($point['time']),
];
}
// Compute
$result = $database->query('SELECT sum(value) AS "value" FROM "appwrite_usage_executions_time" WHERE time > \''.$start.'\' AND time < \''.$end.'\' AND "metric_type"=\'counter\' AND "project"=\''.$project->getId().'\' AND "functionId"=\''.$function->getId().'\' GROUP BY time('.$period[$range]['group'].') FILL(null)');
$points = $result->getPoints();
foreach ($points as $point) {
$compute[] = [
'value' => round((!empty($point['value'])) ? $point['value'] / 1000 : 0, 2), // minutes
'date' => \strtotime($point['time']),
];
}
}
$response->json([
$stats[$metric] = [];
foreach ($requestDocs as $requestDoc) {
$stats[$metric][] = [
'value' => $requestDoc->getAttribute('value'),
'date' => $requestDoc->getAttribute('time'),
];
}
$stats[$metric] = array_reverse($stats[$metric]);
}
});
$usage = new Document([
'range' => $range,
'executions' => [
'data' => $executions,
'total' => \array_sum(\array_map(function ($item) {
return $item['value'];
}, $executions)),
],
'failures' => [
'data' => $failures,
'total' => \array_sum(\array_map(function ($item) {
return $item['value'];
}, $failures)),
],
'compute' => [
'data' => $compute,
'total' => \array_sum(\array_map(function ($item) {
return $item['value'];
}, $compute)),
],
'functions.executions' => $stats["functions.$functionId.executions"],
'functions.failures' => $stats["functions.$functionId.failures"],
'functions.compute' => $stats["functions.$functionId.compute"]
]);
} else {
$response->json([]);
}
$response->dynamic($usage, Response::MODEL_USAGE_FUNCTIONS);
});
App::put('/v1/functions/:functionId')

View file

@ -6,10 +6,14 @@ use Appwrite\Network\Validator\CNAME;
use Appwrite\Network\Validator\Domain as DomainValidator;
use Appwrite\Network\Validator\URL;
use Appwrite\Utopia\Response;
use Utopia\Abuse\Adapters\TimeLimit;
use Utopia\App;
use Utopia\Audit\Audit;
use Utopia\Config\Config;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\UID;
use Utopia\Domains\Domain;
use Utopia\Exception;
@ -19,13 +23,11 @@ use Utopia\Validator\Integer;
use Utopia\Validator\Range;
use Utopia\Validator\Text;
use Utopia\Validator\WhiteList;
use Utopia\Audit\Audit;
use Utopia\Abuse\Adapters\TimeLimit;
App::init(function ($project) {
/** @var Utopia\Database\Document $project */
if($project->getId() !== 'console') {
if ($project->getId() !== 'console') {
throw new Exception('Access to this API is forbidden.', 401);
}
}, ['project'], 'projects');
@ -40,7 +42,7 @@ App::post('/v1/projects')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_PROJECT)
->param('projectId', '', new CustomId(), 'Unique Id. Choose your own unique ID or pass the string `unique()` to auto generate it. Valid chars are a-z, A-Z, 0-9, and underscore. Can\'t start with a leading underscore. Max length is 36 chars.')
->param('projectId', '', new CustomId(), 'Unique Id. Choose your own unique ID or pass the string `unique()` to auto generate it. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('name', null, new Text(128), 'Project name. Max length: 128 chars.')
->param('teamId', '', new UID(), 'Team unique ID.')
->param('description', '', new Text(256), 'Project description. Max length: 256 chars.', true)
@ -69,7 +71,7 @@ App::post('/v1/projects')
if ($team->isEmpty()) {
throw new Exception('Team not found', 404);
}
$auth = Config::getParam('auth', []);
$auths = ['limit' => 0];
foreach ($auth as $index => $method) {
@ -221,22 +223,25 @@ App::get('/v1/projects/:projectId')
});
App::get('/v1/projects/:projectId/usage')
->desc('Get Project')
->desc('Get usage stats for a project')
->groups(['api', 'projects'])
->label('scope', 'projects.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
->label('sdk.method', 'getUsage')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_USAGE_PROJECT)
->param('projectId', '', new UID(), 'Project unique ID.')
->param('range', '30d', new WhiteList(['24h', '7d', '30d', '90d'], true), 'Date range.', true)
->inject('response')
->inject('dbForConsole')
->inject('projectDB')
->inject('dbForInternal')
->inject('register')
->action(function ($projectId, $range, $response, $dbForConsole, $projectDB, $register) {
->action(function ($projectId, $range, $response, $dbForConsole, $dbForInternal, $register) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForConsole */
/** @var Appwrite\Database\Database $projectDB */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Registry\Registry $register */
$project = $dbForConsole->getDocument('projects', $projectId);
@ -245,172 +250,72 @@ App::get('/v1/projects/:projectId/usage')
throw new Exception('Project not found', 404);
}
$usage = [];
if (App::getEnv('_APP_USAGE_STATS', 'enabled') == 'enabled') {
$period = [
'24h' => [
'start' => DateTime::createFromFormat('U', \strtotime('-24 hours')),
'end' => DateTime::createFromFormat('U', \strtotime('+1 hour')),
'group' => '30m',
'period' => '30m',
'limit' => 48,
],
'7d' => [
'start' => DateTime::createFromFormat('U', \strtotime('-7 days')),
'end' => DateTime::createFromFormat('U', \strtotime('now')),
'group' => '1d',
'period' => '1d',
'limit' => 7,
],
'30d' => [
'start' => DateTime::createFromFormat('U', \strtotime('-30 days')),
'end' => DateTime::createFromFormat('U', \strtotime('now')),
'group' => '1d',
'period' => '1d',
'limit' => 30,
],
'90d' => [
'start' => DateTime::createFromFormat('U', \strtotime('-90 days')),
'end' => DateTime::createFromFormat('U', \strtotime('now')),
'group' => '1d',
'period' => '1d',
'limit' => 90,
],
];
$client = $register->get('influxdb');
$dbForInternal->setNamespace('project_' . $projectId . '_internal');
$requests = [];
$network = [];
$functions = [];
$metrics = [
'requests',
'network',
'executions',
'users.count',
'database.documents.count',
'database.collections.count',
'storage.total'
];
if ($client) {
$start = $period[$range]['start']->format(DateTime::RFC3339);
$end = $period[$range]['end']->format(DateTime::RFC3339);
$database = $client->selectDB('telegraf');
$stats = [];
// Requests
$result = $database->query('SELECT sum(value) AS "value" FROM "appwrite_usage_requests_all" WHERE time > \'' . $start . '\' AND time < \'' . $end . '\' AND "metric_type"=\'counter\' AND "project"=\'' . $project->getId() . '\' GROUP BY time(' . $period[$range]['group'] . ') FILL(null)');
$points = $result->getPoints();
Authorization::skip(function() use ($dbForInternal, $period, $range, $metrics, &$stats) {
foreach ($metrics as $metric) {
$requestDocs = $dbForInternal->find('stats', [
new Query('period', Query::TYPE_EQUAL, [$period[$range]['period']]),
new Query('metric', Query::TYPE_EQUAL, [$metric]),
], $period[$range]['limit'], 0, ['time'], [Database::ORDER_DESC]);
$stats[$metric] = [];
foreach ($requestDocs as $requestDoc) {
$stats[$metric][] = [
'value' => $requestDoc->getAttribute('value'),
'date' => $requestDoc->getAttribute('time'),
];
}
$stats[$metric] = array_reverse($stats[$metric]);
}
});
foreach ($points as $point) {
$requests[] = [
'value' => (!empty($point['value'])) ? $point['value'] : 0,
'date' => \strtotime($point['time']),
];
}
// Network
$result = $database->query('SELECT sum(value) AS "value" FROM "appwrite_usage_network_all" WHERE time > \'' . $start . '\' AND time < \'' . $end . '\' AND "metric_type"=\'counter\' AND "project"=\'' . $project->getId() . '\' GROUP BY time(' . $period[$range]['group'] . ') FILL(null)');
$points = $result->getPoints();
foreach ($points as $point) {
$network[] = [
'value' => (!empty($point['value'])) ? $point['value'] : 0,
'date' => \strtotime($point['time']),
];
}
// Functions
$result = $database->query('SELECT sum(value) AS "value" FROM "appwrite_usage_executions_all" WHERE time > \'' . $start . '\' AND time < \'' . $end . '\' AND "metric_type"=\'counter\' AND "project"=\'' . $project->getId() . '\' GROUP BY time(' . $period[$range]['group'] . ') FILL(null)');
$points = $result->getPoints();
foreach ($points as $point) {
$functions[] = [
'value' => (!empty($point['value'])) ? $point['value'] : 0,
'date' => \strtotime($point['time']),
];
}
}
} else {
$requests = [];
$network = [];
$functions = [];
}
// Users
$projectDB->getCollection([
'limit' => 0,
'offset' => 0,
'filters' => [
'$collection=users',
],
]);
$usersTotal = $projectDB->getSum();
// Documents
$collections = $projectDB->getCollection([
'limit' => 100,
'offset' => 0,
'filters' => [
'$collection=collections',
],
]);
$collectionsTotal = $projectDB->getSum();
$documents = [];
foreach ($collections as $collection) {
$result = $projectDB->getCollection([
'limit' => 0,
'offset' => 0,
'filters' => [
'$collection=' . $collection['$id'],
],
$usage = new Document([
'range' => $range,
'requests' => $stats['requests'],
'network' => $stats['network'],
'functions' => $stats['executions'],
'documents' => $stats['database.documents.count'],
'collections' => $stats['database.collections.count'],
'users' => $stats['users.count'],
'storage' => $stats['storage.total']
]);
$documents[] = ['name' => $collection['name'], 'total' => $projectDB->getSum()];
}
$response->json([
'range' => $range,
'requests' => [
'data' => $requests,
'total' => \array_sum(\array_map(function ($item) {
return $item['value'];
}, $requests)),
],
'network' => [
'data' => \array_map(function ($value) {return ['value' => \round($value['value'] / 1000000, 2), 'date' => $value['date']];}, $network), // convert bytes to mb
'total' => \array_sum(\array_map(function ($item) {
return $item['value'];
}, $network)),
],
'functions' => [
'data' => $functions,
'total' => \array_sum(\array_map(function ($item) {
return $item['value'];
}, $functions)),
],
'collections' => [
'data' => $collections,
'total' => $collectionsTotal,
],
'documents' => [
'data' => $documents,
'total' => \array_sum(\array_map(function ($item) {
return $item['total'];
}, $documents)),
],
'users' => [
'data' => [],
'total' => $usersTotal,
],
'storage' => [
'total' => $projectDB->getCount(
[
'attribute' => 'sizeOriginal',
'filters' => [
'$collection=files',
],
]
) +
$projectDB->getCount(
[
'attribute' => 'size',
'filters' => [
'$collection=tags',
],
]
),
],
]);
$response->dynamic($usage, Response::MODEL_USAGE_PROJECT);
});
App::patch('/v1/projects/:projectId')
@ -474,7 +379,7 @@ App::patch('/v1/projects/:projectId/service')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_PROJECT)
->param('projectId', '', new UID(), 'Project unique ID.')
->param('service', '', new WhiteList(array_keys(array_filter(Config::getParam('services'), function($element) {return $element['optional'];})), true), 'Service name.')
->param('service', '', new WhiteList(array_keys(array_filter(Config::getParam('services'), function ($element) {return $element['optional'];})), true), 'Service name.')
->param('status', null, new Boolean(), 'Service status.')
->inject('response')
->inject('dbForConsole')

View file

@ -10,6 +10,7 @@ use Utopia\Validator\HexColor;
use Utopia\Cache\Cache;
use Utopia\Cache\Adapter\Filesystem;
use Appwrite\ClamAV\Network;
use Utopia\Database\Validator\Authorization;
use Appwrite\Database\Validator\CustomId;
use Utopia\Database\Document;
use Utopia\Database\Validator\UID;
@ -22,6 +23,7 @@ use Utopia\Image\Image;
use Appwrite\OpenSSL\OpenSSL;
use Appwrite\Utopia\Response;
use Utopia\Config\Config;
use Utopia\Database\Database;
use Utopia\Database\Query;
App::post('/v1/storage/files')
@ -38,7 +40,7 @@ App::post('/v1/storage/files')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_FILE)
->param('fileId', '', new CustomId(), 'Unique Id. Choose your own unique ID or pass the string `unique()` to auto generate it. Valid chars are a-z, A-Z, 0-9, and underscore. Can\'t start with a leading underscore. Max length is 36 chars.')
->param('fileId', '', new CustomId(), 'Unique Id. Choose your own unique ID or pass the string `unique()` to auto generate it. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('file', [], new File(), 'Binary file.', false)
->param('read', null, new ArrayList(new Text(64)), 'An array of strings with read permissions. By default only the current user is granted with read permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.', true)
->param('write', null, new ArrayList(new Text(64)), 'An array of strings with write permissions. By default only the current user is granted with write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.', true)
@ -54,7 +56,7 @@ App::post('/v1/storage/files')
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Document $user */
/** @var Appwrite\Event\Event $audits */
/** @var Appwrite\Event\Event $usage */
/** @var Appwrite\Stats\Stats $usage */
$file = $request->getFiles('file');
@ -147,11 +149,13 @@ App::post('/v1/storage/files')
$audits
->setParam('event', 'storage.files.create')
->setParam('resource', 'storage/files/'.$file->getId())
->setParam('resource', 'file/'.$file->getId())
;
$usage
->setParam('storage', $sizeActual)
->setParam('storage.files.create', 1)
->setParam('bucketId', 'default')
;
$response->setStatusCode(Response::STATUS_CODE_CREATED);
@ -177,9 +181,11 @@ App::get('/v1/storage/files')
->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true)
->inject('response')
->inject('dbForInternal')
->action(function ($search, $limit, $offset, $after, $orderType, $response, $dbForInternal) {
->inject('usage')
->action(function ($search, $limit, $offset, $after, $orderType, $response, $dbForInternal, $usage) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Appwrite\Stats\Stats $usage */
if (!empty($after)) {
$afterFile = $dbForInternal->getDocument('files', $after);
@ -195,6 +201,11 @@ App::get('/v1/storage/files')
$queries[] = new Query('search', Query::TYPE_SEARCH, [$search]);
}
$usage
->setParam('storage.files.read', 1)
->setParam('bucketId', 'default')
;
$response->dynamic(new Document([
'files' => $dbForInternal->find('files', $queries, $limit, $offset, [], [$orderType], $afterFile ?? null),
'sum' => $dbForInternal->count('files', $queries, APP_LIMIT_COUNT),
@ -215,16 +226,21 @@ App::get('/v1/storage/files/:fileId')
->param('fileId', '', new UID(), 'File unique ID.')
->inject('response')
->inject('dbForInternal')
->action(function ($fileId, $response, $dbForInternal) {
->inject('usage')
->action(function ($fileId, $response, $dbForInternal, $usage) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Appwrite\Stats\Stats $usage */
$file = $dbForInternal->getDocument('files', $fileId);
if (empty($file->getId())) {
throw new Exception('File not found', 404);
}
$usage
->setParam('storage.files.read', 1)
->setParam('bucketId', 'default')
;
$response->dynamic($file, Response::MODEL_FILE);
});
@ -255,11 +271,13 @@ App::get('/v1/storage/files/:fileId/preview')
->inject('response')
->inject('project')
->inject('dbForInternal')
->action(function ($fileId, $width, $height, $gravity, $quality, $borderWidth, $borderColor, $borderRadius, $opacity, $rotation, $background, $output, $request, $response, $project, $dbForInternal) {
->inject('usage')
->action(function ($fileId, $width, $height, $gravity, $quality, $borderWidth, $borderColor, $borderRadius, $opacity, $rotation, $background, $output, $request, $response, $project, $dbForInternal, $usage) {
/** @var Utopia\Swoole\Request $request */
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Document $project */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Appwrite\Stats\Stats $stats */
$storage = 'files';
@ -372,6 +390,11 @@ App::get('/v1/storage/files/:fileId/preview')
$cache->save($key, $data);
$usage
->setParam('storage.files.read', 1)
->setParam('bucketId', 'default')
;
$response
->setContentType($outputs[$output])
->addHeader('Expires', $date)
@ -396,9 +419,11 @@ App::get('/v1/storage/files/:fileId/download')
->param('fileId', '', new UID(), 'File unique ID.')
->inject('response')
->inject('dbForInternal')
->action(function ($fileId, $response, $dbForInternal) {
->inject('usage')
->action(function ($fileId, $response, $dbForInternal, $usage) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Appwrite\Stats\Stats $usage */
$file = $dbForInternal->getDocument('files', $fileId);
@ -430,6 +455,11 @@ App::get('/v1/storage/files/:fileId/download')
$source = $compressor->decompress($source);
$usage
->setParam('storage.files.read', 1)
->setParam('bucketId', 'default')
;
// Response
$response
->setContentType($file->getAttribute('mimeType'))
@ -454,9 +484,11 @@ App::get('/v1/storage/files/:fileId/view')
->param('fileId', '', new UID(), 'File unique ID.')
->inject('response')
->inject('dbForInternal')
->action(function ($fileId, $response, $dbForInternal) {
->inject('usage')
->action(function ($fileId, $response, $dbForInternal, $usage) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Appwrite\Stats\Stats $usage */
$file = $dbForInternal->getDocument('files', $fileId);
$mimes = Config::getParam('storage-mimes');
@ -496,6 +528,11 @@ App::get('/v1/storage/files/:fileId/view')
$output = $compressor->decompress($source);
$fileName = $file->getAttribute('name', '');
$usage
->setParam('storage.files.read', 1)
->setParam('bucketId', 'default')
;
// Response
$response
->setContentType($contentType)
@ -526,7 +563,8 @@ App::put('/v1/storage/files/:fileId')
->inject('response')
->inject('dbForInternal')
->inject('audits')
->action(function ($fileId, $read, $write, $response, $dbForInternal, $audits) {
->inject('usage')
->action(function ($fileId, $read, $write, $response, $dbForInternal, $audits, $usage) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Appwrite\Event\Event $audits */
@ -545,7 +583,12 @@ App::put('/v1/storage/files/:fileId')
$audits
->setParam('event', 'storage.files.update')
->setParam('resource', 'storage/files/'.$file->getId())
->setParam('resource', 'file/'.$file->getId())
;
$usage
->setParam('storage.files.update', 1)
->setParam('bucketId', 'default')
;
$response->dynamic($file, Response::MODEL_FILE);
@ -573,7 +616,7 @@ App::delete('/v1/storage/files/:fileId')
/** @var Utopia\Database\Database $dbForInternal */
/** @var Appwrite\Event\Event $events */
/** @var Appwrite\Event\Event $audits */
/** @var Appwrite\Event\Event $usage */
/** @var Appwrite\Stats\Stats $usage */
$file = $dbForInternal->getDocument('files', $fileId);
@ -591,11 +634,13 @@ App::delete('/v1/storage/files/:fileId')
$audits
->setParam('event', 'storage.files.delete')
->setParam('resource', 'storage/files/'.$file->getId())
->setParam('resource', 'file/'.$file->getId())
;
$usage
->setParam('storage', $file->getAttribute('size', 0) * -1)
->setParam('storage.files.delete', 1)
->setParam('bucketId', 'default')
;
$events
@ -603,4 +648,159 @@ App::delete('/v1/storage/files/:fileId')
;
$response->noContent();
});
App::get('/v1/storage/usage')
->desc('Get usage stats for storage')
->groups(['api', 'storage'])
->label('scope', 'files.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'storage')
->label('sdk.method', 'getUsage')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_USAGE_STORAGE)
->param('range', '30d', new WhiteList(['24h', '7d', '30d', '90d'], true), 'Date range.', true)
->inject('response')
->inject('dbForInternal')
->action(function ($range, $response, $dbForInternal) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
$usage = [];
if (App::getEnv('_APP_USAGE_STATS', 'enabled') == 'enabled') {
$period = [
'24h' => [
'period' => '30m',
'limit' => 48,
],
'7d' => [
'period' => '1d',
'limit' => 7,
],
'30d' => [
'period' => '1d',
'limit' => 30,
],
'90d' => [
'period' => '1d',
'limit' => 90,
],
];
$metrics = [
"storage.total",
"storage.files.count"
];
$stats = [];
Authorization::skip(function() use ($dbForInternal, $period, $range, $metrics, &$stats) {
foreach ($metrics as $metric) {
$requestDocs = $dbForInternal->find('stats', [
new Query('period', Query::TYPE_EQUAL, [$period[$range]['period']]),
new Query('metric', Query::TYPE_EQUAL, [$metric]),
], $period[$range]['limit'], 0, ['time'], [Database::ORDER_DESC]);
$stats[$metric] = [];
foreach ($requestDocs as $requestDoc) {
$stats[$metric][] = [
'value' => $requestDoc->getAttribute('value'),
'date' => $requestDoc->getAttribute('time'),
];
}
$stats[$metric] = array_reverse($stats[$metric]);
}
});
$usage = new Document([
'range' => $range,
'storage' => $stats['storage.total'],
'files' => $stats['storage.files.count']
]);
}
$response->dynamic($usage, Response::MODEL_USAGE_STORAGE);
});
App::get('/v1/storage/:bucketId/usage')
->desc('Get usage stats for a storage bucket')
->groups(['api', 'storage'])
->label('scope', 'files.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'storage')
->label('sdk.method', 'getBucketUsage')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_USAGE_BUCKETS)
->param('bucketId', '', new UID(), 'Bucket unique ID.')
->param('range', '30d', new WhiteList(['24h', '7d', '30d', '90d'], true), 'Date range.', true)
->inject('response')
->inject('dbForInternal')
->action(function ($bucketId, $range, $response, $dbForInternal) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
// TODO: Check if the storage bucket exists else throw 404
$usage = [];
if (App::getEnv('_APP_USAGE_STATS', 'enabled') == 'enabled') {
$period = [
'24h' => [
'period' => '30m',
'limit' => 48,
],
'7d' => [
'period' => '1d',
'limit' => 7,
],
'30d' => [
'period' => '1d',
'limit' => 30,
],
'90d' => [
'period' => '1d',
'limit' => 90,
],
];
$metrics = [
"storage.buckets.$bucketId.files.count",
"storage.buckets.$bucketId.files.create",
"storage.buckets.$bucketId.files.read",
"storage.buckets.$bucketId.files.update",
"storage.buckets.$bucketId.files.delete"
];
$stats = [];
Authorization::skip(function() use ($dbForInternal, $period, $range, $metrics, &$stats) {
foreach ($metrics as $metric) {
$requestDocs = $dbForInternal->find('stats', [
new Query('period', Query::TYPE_EQUAL, [$period[$range]['period']]),
new Query('metric', Query::TYPE_EQUAL, [$metric]),
], $period[$range]['limit'], 0, ['time'], [Database::ORDER_DESC]);
$stats[$metric] = [];
foreach ($requestDocs as $requestDoc) {
$stats[$metric][] = [
'value' => $requestDoc->getAttribute('value'),
'date' => $requestDoc->getAttribute('time'),
];
}
$stats[$metric] = array_reverse($stats[$metric]);
}
});
$usage = new Document([
'range' => $range,
'files.count' => $stats["storage.buckets.$bucketId.files.count"],
'files.create' => $stats["storage.buckets.$bucketId.files.create"],
'files.read' => $stats["storage.buckets.$bucketId.files.read"],
'files.update' => $stats["storage.buckets.$bucketId.files.update"],
'files.delete' => $stats["storage.buckets.$bucketId.files.delete"]
]);
}
$response->dynamic($usage, Response::MODEL_USAGE_BUCKETS);
});

View file

@ -33,7 +33,7 @@ App::post('/v1/teams')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_TEAM)
->param('teamId', '', new CustomId(), 'Unique Id. Choose your own unique ID or pass the string `unique()` to auto generate it. Valid chars are a-z, A-Z, 0-9, and underscore. Can\'t start with a leading underscore. Max length is 36 chars.')
->param('teamId', '', new CustomId(), 'Unique Id. Choose your own unique ID or pass the string `unique()` to auto generate it. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('name', null, new Text(128), 'Team name. Max length: 128 chars.')
->param('roles', ['owner'], new ArrayList(new Key()), 'Array of strings. Use this param to set the roles in the team for the user who created it. The default role is **owner**. A role can be any string. Learn more about [roles and permissions](/docs/permissions). Max length for each role is 32 chars.', true)
->inject('response')
@ -408,7 +408,7 @@ App::post('/v1/teams/:teamId/memberships')
$audits
->setParam('userId', $invitee->getId())
->setParam('event', 'teams.memberships.create')
->setParam('resource', 'teams/'.$teamId)
->setParam('resource', 'team/'.$teamId)
;
$response->setStatusCode(Response::STATUS_CODE_CREATED);
@ -570,7 +570,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId')
$audits
->setParam('userId', $user->getId())
->setParam('event', 'teams.memberships.update')
->setParam('resource', 'teams/'.$teamId)
->setParam('resource', 'team/'.$teamId)
;
$response->dynamic($membership, Response::MODEL_MEMBERSHIP);
@ -695,7 +695,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
$audits
->setParam('userId', $user->getId())
->setParam('event', 'teams.memberships.update.status')
->setParam('resource', 'teams/'.$teamId)
->setParam('resource', 'team/'.$teamId)
;
if (!Config::getParam('domainVerification')) {
@ -788,7 +788,7 @@ App::delete('/v1/teams/:teamId/memberships/:membershipId')
$audits
->setParam('userId', $membership->getAttribute('userId'))
->setParam('event', 'teams.memberships.delete')
->setParam('resource', 'teams/'.$teamId)
->setParam('resource', 'team/'.$teamId)
;
$events

View file

@ -17,7 +17,10 @@ use Utopia\Database\Exception\Duplicate;
use Utopia\Database\Validator\UID;
use DeviceDetector\DeviceDetector;
use Appwrite\Database\Validator\CustomId;
use Utopia\Config\Config;
use Utopia\Database\Database;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
App::post('/v1/users')
->desc('Create User')
@ -31,15 +34,17 @@ App::post('/v1/users')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_USER)
->param('userId', '', new CustomId(), 'Unique Id. Choose your own unique ID or pass the string `unique()` to auto generate it. Valid chars are a-z, A-Z, 0-9, and underscore. Can\'t start with a leading underscore. Max length is 36 chars.')
->param('userId', '', new CustomId(), 'Unique Id. Choose your own unique ID or pass the string `unique()` to auto generate it. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('email', '', new Email(), 'User email.')
->param('password', '', new Password(), 'User password. Must be between 6 to 32 chars.')
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
->inject('response')
->inject('dbForInternal')
->action(function ($userId, $email, $password, $name, $response, $dbForInternal) {
->inject('usage')
->action(function ($userId, $email, $password, $name, $response, $dbForInternal, $usage) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Appwrite\Stats\Stats $usage */
$email = \strtolower($email);
@ -67,6 +72,10 @@ App::post('/v1/users')
throw new Exception('Account already exists', 409);
}
$usage
->setParam('users.create', 1)
;
$response->setStatusCode(Response::STATUS_CODE_CREATED);
$response->dynamic($user, Response::MODEL_USER);
});
@ -89,9 +98,11 @@ App::get('/v1/users')
->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true)
->inject('response')
->inject('dbForInternal')
->action(function ($search, $limit, $offset, $after, $orderType, $response, $dbForInternal) {
->inject('usage')
->action(function ($search, $limit, $offset, $after, $orderType, $response, $dbForInternal, $usage) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Appwrite\Stats\Stats $usage */
if (!empty($after)) {
$afterUser = $dbForInternal->getDocument('users', $after);
@ -107,12 +118,13 @@ App::get('/v1/users')
$queries[] = new Query('search', Query::TYPE_SEARCH, [$search]);
}
$results = $dbForInternal->find('users', $queries, $limit, $offset, ['_id'], [$orderType]);
$sum = $dbForInternal->count('users', $queries, APP_LIMIT_COUNT);
$usage
->setParam('users.read', 1)
;
$response->dynamic(new Document([
'users' => $results,
'sum' => $sum,
'users' => $dbForInternal->find('users', [], $limit, $offset, [], [$orderType], $afterUser ?? null),
'sum' => $dbForInternal->count('users', [], APP_LIMIT_COUNT),
]), Response::MODEL_USER_LIST);
});
@ -130,9 +142,11 @@ App::get('/v1/users/:userId')
->param('userId', '', new UID(), 'User unique ID.')
->inject('response')
->inject('dbForInternal')
->action(function ($userId, $response, $dbForInternal) {
->inject('usage')
->action(function ($userId, $response, $dbForInternal, $usage) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Appwrite\Stats\Stats $usage */
$user = $dbForInternal->getDocument('users', $userId);
@ -140,6 +154,9 @@ App::get('/v1/users/:userId')
throw new Exception('User not found', 404);
}
$usage
->setParam('users.read', 1)
;
$response->dynamic($user, Response::MODEL_USER);
});
@ -157,9 +174,11 @@ App::get('/v1/users/:userId/prefs')
->param('userId', '', new UID(), 'User unique ID.')
->inject('response')
->inject('dbForInternal')
->action(function ($userId, $response, $dbForInternal) {
->inject('usage')
->action(function ($userId, $response, $dbForInternal, $usage) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Appwrite\Stats\Stats $usage */
$user = $dbForInternal->getDocument('users', $userId);
@ -169,6 +188,9 @@ App::get('/v1/users/:userId/prefs')
$prefs = $user->getAttribute('prefs', new \stdClass());
$usage
->setParam('users.read', 1)
;
$response->dynamic(new Document($prefs), Response::MODEL_PREFERENCES);
});
@ -187,10 +209,12 @@ App::get('/v1/users/:userId/sessions')
->inject('response')
->inject('dbForInternal')
->inject('locale')
->action(function ($userId, $response, $dbForInternal, $locale) {
->inject('usage')
->action(function ($userId, $response, $dbForInternal, $locale, $usage) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Locale\Locale $locale */
/** @var Appwrite\Stats\Stats $usage */
$user = $dbForInternal->getDocument('users', $userId);
@ -210,6 +234,9 @@ App::get('/v1/users/:userId/sessions')
$sessions[$key] = $session;
}
$usage
->setParam('users.read', 1)
;
$response->dynamic(new Document([
'sessions' => $sessions,
'sum' => count($sessions),
@ -232,12 +259,14 @@ App::get('/v1/users/:userId/logs')
->inject('dbForInternal')
->inject('locale')
->inject('geodb')
->action(function ($userId, $response, $dbForInternal, $locale, $geodb) {
->inject('usage')
->action(function ($userId, $response, $dbForInternal, $locale, $geodb, $usage) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Document $project */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Locale\Locale $locale */
/** @var MaxMind\Db\Reader $geodb */
/** @var Appwrite\Stats\Stats $usage */
$user = $dbForInternal->getDocument('users', $userId);
@ -319,6 +348,9 @@ App::get('/v1/users/:userId/logs')
}
}
$usage
->setParam('users.read', 1)
;
$response->dynamic(new Document(['logs' => $output]), Response::MODEL_LOG_LIST);
});
@ -338,9 +370,11 @@ App::patch('/v1/users/:userId/status')
->param('status', null, new Boolean(true), 'User Status. To activate the user pass `true` and to block the user pass `false`')
->inject('response')
->inject('dbForInternal')
->action(function ($userId, $status, $response, $dbForInternal) {
->inject('usage')
->action(function ($userId, $status, $response, $dbForInternal, $usage) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Appwrite\Stats\Stats $usage */
$user = $dbForInternal->getDocument('users', $userId);
@ -350,6 +384,9 @@ App::patch('/v1/users/:userId/status')
$user = $dbForInternal->updateDocument('users', $user->getId(), $user->setAttribute('status', (bool) $status));
$usage
->setParam('users.update', 1)
;
$response->dynamic($user, Response::MODEL_USER);
});
@ -369,9 +406,11 @@ App::patch('/v1/users/:userId/verification')
->param('emailVerification', false, new Boolean(), 'User Email Verification Status.')
->inject('response')
->inject('dbForInternal')
->action(function ($userId, $emailVerification, $response, $dbForInternal) {
->inject('usage')
->action(function ($userId, $emailVerification, $response, $dbForInternal, $usage) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Appwrite\Stats\Stats $usage */
$user = $dbForInternal->getDocument('users', $userId);
@ -381,6 +420,132 @@ App::patch('/v1/users/:userId/verification')
$user = $dbForInternal->updateDocument('users', $user->getId(), $user->setAttribute('emailVerification', $emailVerification));
$usage
->setParam('users.update', 1)
;
$response->dynamic($user, Response::MODEL_USER);
});
App::patch('/v1/users/:userId/name')
->desc('Update Name')
->groups(['api', 'users'])
->label('event', 'users.update.name')
->label('scope', 'users.write')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'users')
->label('sdk.method', 'updateName')
->label('sdk.description', '/docs/references/users/update-user-name.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_USER)
->param('userId', '', new UID(), 'User unique ID.')
->param('name', '', new Text(128), 'User name. Max length: 128 chars.')
->inject('response')
->inject('dbForInternal')
->inject('audits')
->action(function ($userId, $name, $response, $dbForInternal, $audits) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Appwrite\Event\Event $audits */
$user = $dbForInternal->getDocument('users', $userId);
if ($user->isEmpty()) {
throw new Exception('User not found', 404);
}
$user = $dbForInternal->updateDocument('users', $user->getId(), $user->setAttribute('name', $name));
$audits
->setParam('userId', $user->getId())
->setParam('event', 'users.update.name')
->setParam('resource', 'user/'.$user->getId())
;
$response->dynamic($user, Response::MODEL_USER);
});
App::patch('/v1/users/:userId/password')
->desc('Update Password')
->groups(['api', 'users'])
->label('event', 'users.update.password')
->label('scope', 'users.write')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'users')
->label('sdk.method', 'updatePassword')
->label('sdk.description', '/docs/references/users/update-user-password.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_USER)
->param('userId', '', new UID(), 'User unique ID.')
->param('password', '', new Password(), 'New user password. Must be between 6 to 32 chars.')
->inject('response')
->inject('dbForInternal')
->inject('audits')
->action(function ($userId, $password, $response, $dbForInternal, $audits) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Appwrite\Event\Event $audits */
$user = $dbForInternal->getDocument('users', $userId);
if ($user->isEmpty()) {
throw new Exception('User not found', 404);
}
$user = $dbForInternal->updateDocument('users', $user->getId(), $user->setAttribute('password', Auth::passwordHash($password))
->setAttribute('passwordUpdate', \time()));
$audits
->setParam('userId', $user->getId())
->setParam('event', 'users.update.password')
->setParam('resource', 'user/'.$user->getId())
;
$response->dynamic($user, Response::MODEL_USER);
});
App::patch('/v1/users/:userId/email')
->desc('Update Email')
->groups(['api', 'users'])
->label('event', 'users.update.email')
->label('scope', 'users.write')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'users')
->label('sdk.method', 'updateEmail')
->label('sdk.description', '/docs/references/users/update-user-email.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_USER)
->param('userId', '', new UID(), 'User unique ID.')
->param('email', '', new Email(), 'User email.')
->inject('response')
->inject('dbForInternal')
->inject('audits')
->action(function ($userId, $email, $response, $dbForInternal, $audits) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Appwrite\Event\Event $audits */
$user = $dbForInternal->getDocument('users', $userId);
if ($user->isEmpty()) {
throw new Exception('User not found', 404);
}
$email = \strtolower($email);
try {
$user = $dbForInternal->updateDocument('users', $user->getId(), $user->setAttribute('email', $email));
} catch(Duplicate $th) {
throw new Exception('Email already exists', 409);
}
$audits
->setParam('userId', $user->getId())
->setParam('event', 'account.update.email')
->setParam('resource', 'user/'.$user->getId())
;
$response->dynamic($user, Response::MODEL_USER);
});
@ -400,9 +565,11 @@ App::patch('/v1/users/:userId/prefs')
->param('prefs', '', new Assoc(), 'Prefs key-value JSON object.')
->inject('response')
->inject('dbForInternal')
->action(function ($userId, $prefs, $response, $dbForInternal) {
->inject('usage')
->action(function ($userId, $prefs, $response, $dbForInternal, $usage) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Appwrite\Stats\Stats $usage */
$user = $dbForInternal->getDocument('users', $userId);
@ -412,6 +579,9 @@ App::patch('/v1/users/:userId/prefs')
$user = $dbForInternal->updateDocument('users', $user->getId(), $user->setAttribute('prefs', $prefs));
$usage
->setParam('users.update', 1)
;
$response->dynamic(new Document($prefs), Response::MODEL_PREFERENCES);
});
@ -431,10 +601,12 @@ App::delete('/v1/users/:userId/sessions/:sessionId')
->inject('response')
->inject('dbForInternal')
->inject('events')
->action(function ($userId, $sessionId, $response, $dbForInternal, $events) {
->inject('usage')
->action(function ($userId, $sessionId, $response, $dbForInternal, $events, $usage) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Appwrite\Event\Event $events */
/** @var Appwrite\Stats\Stats $usage */
$user = $dbForInternal->getDocument('users', $userId);
@ -461,6 +633,11 @@ App::delete('/v1/users/:userId/sessions/:sessionId')
}
}
$usage
->setParam('users.update', 1)
->setParam('users.sessions.delete', 1)
;
$response->noContent();
});
@ -479,10 +656,12 @@ App::delete('/v1/users/:userId/sessions')
->inject('response')
->inject('dbForInternal')
->inject('events')
->action(function ($userId, $response, $dbForInternal, $events) {
->inject('usage')
->action(function ($userId, $response, $dbForInternal, $events, $usage) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Appwrite\Event\Event $events */
/** @var Appwrite\Stats\Stats $usage */
$user = $dbForInternal->getDocument('users', $userId);
@ -502,6 +681,10 @@ App::delete('/v1/users/:userId/sessions')
->setParam('eventData', $response->output($user, Response::MODEL_USER))
;
$usage
->setParam('users.update', 1)
->setParam('users.sessions.delete', 1)
;
$response->noContent();
});
@ -521,11 +704,13 @@ App::delete('/v1/users/:userId')
->inject('dbForInternal')
->inject('events')
->inject('deletes')
->action(function ($userId, $response, $dbForInternal, $events, $deletes) {
->inject('usage')
->action(function ($userId, $response, $dbForInternal, $events, $deletes, $usage) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Appwrite\Event\Event $events */
/** @var Appwrite\Event\Event $deletes */
/** @var Appwrite\Stats\Stats $usage */
$user = $dbForInternal->getDocument('users', $userId);
@ -546,5 +731,96 @@ App::delete('/v1/users/:userId')
->setParam('eventData', $response->output($user, Response::MODEL_USER))
;
$usage
->setParam('users.delete', 1)
;
$response->noContent();
});
App::get('/v1/users/usage')
->desc('Get usage stats for the users API')
->groups(['api', 'users'])
->label('scope', 'users.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'users')
->label('sdk.method', 'getUsage')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_USAGE_USERS)
->param('range', '30d', new WhiteList(['24h', '7d', '30d', '90d'], true), 'Date range.', true)
->param('provider', '', new WhiteList(\array_merge(['email', 'anonymous'], \array_map(function($value) { return "oauth-".$value; }, \array_keys(Config::getParam('providers', [])))), true), 'Provider Name.', true)
->inject('response')
->inject('dbForInternal')
->inject('register')
->action(function ($range, $provider, $response, $dbForInternal) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
$usage = [];
if (App::getEnv('_APP_USAGE_STATS', 'enabled') == 'enabled') {
$period = [
'24h' => [
'period' => '30m',
'limit' => 48,
],
'7d' => [
'period' => '1d',
'limit' => 7,
],
'30d' => [
'period' => '1d',
'limit' => 30,
],
'90d' => [
'period' => '1d',
'limit' => 90,
],
];
$metrics = [
"users.count",
"users.create",
"users.read",
"users.update",
"users.delete",
"users.sessions.create",
"users.sessions.$provider.create",
"users.sessions.delete"
];
$stats = [];
Authorization::skip(function() use ($dbForInternal, $period, $range, $metrics, &$stats) {
foreach ($metrics as $metric) {
$requestDocs = $dbForInternal->find('stats', [
new Query('period', Query::TYPE_EQUAL, [$period[$range]['period']]),
new Query('metric', Query::TYPE_EQUAL, [$metric]),
], $period[$range]['limit'], 0, ['time'], [Database::ORDER_DESC]);
$stats[$metric] = [];
foreach ($requestDocs as $requestDoc) {
$stats[$metric][] = [
'value' => $requestDoc->getAttribute('value'),
'date' => $requestDoc->getAttribute('time'),
];
}
$stats[$metric] = array_reverse($stats[$metric]);
}
});
$usage = new Document([
'range' => $range,
'users.count' => $stats["users.count"],
'users.create' => $stats["users.create"],
'users.read' => $stats["users.read"],
'users.update' => $stats["users.update"],
'users.delete' => $stats["users.delete"],
'sessions.create' => $stats["users.sessions.create"],
'sessions.provider.create' => $stats["users.sessions.$provider.create"],
'sessions.delete' => $stats["users.sessions.delete"]
]);
}
$response->dynamic($usage, Response::MODEL_USAGE_USERS);
});

View file

@ -319,7 +319,7 @@ App::error(function ($error, $utopia, $request, $response, $layout, $project) {
if($route) {
Console::error('[Error] Method: '.$route->getMethod());
Console::error('[Error] URL: '.$route->getURL());
Console::error('[Error] URL: '.$route->getPath());
}
Console::error('[Error] Type: '.get_class($error));

View file

@ -518,7 +518,7 @@ App::shutdown(function($utopia, $response, $request) {
throw new Exception('Failed to read results', 500);
}
$result[$route->getMethod() . ':' . $route->getURL()] = true;
$result[$route->getMethod() . ':' . $route->getPath()] = true;
$tests = \array_merge($tests, $result);
@ -526,5 +526,5 @@ App::shutdown(function($utopia, $response, $request) {
throw new Exception('Failed to save results', 500);
}
$response->dynamic(new Document(['result' => $route->getMethod() . ':' . $route->getURL() . ':passed']), Response::MODEL_MOCK);
$response->dynamic(new Document(['result' => $route->getMethod() . ':' . $route->getPath() . ':passed']), Response::MODEL_MOCK);
}, ['utopia', 'response', 'request'], 'mock');

View file

@ -41,7 +41,7 @@ App::init(function ($utopia, $request, $response, $project, $user, $events, $aud
->setParam('{userId}', $user->getId())
->setParam('{userAgent}', $request->getUserAgent(''))
->setParam('{ip}', $request->getIP())
->setParam('{url}', $request->getHostname().$route->getURL())
->setParam('{url}', $request->getHostname().$route->getPath())
;
// TODO make sure we get array here
@ -104,6 +104,7 @@ App::init(function ($utopia, $request, $response, $project, $user, $events, $aud
->setParam('httpRequest', 1)
->setParam('httpUrl', $request->getHostname().$request->getURI())
->setParam('httpMethod', $request->getMethod())
->setParam('httpPath', $route->getPath())
->setParam('networkRequestSize', 0)
->setParam('networkResponseSize', 0)
->setParam('storage', 0)

View file

@ -387,11 +387,10 @@ App::get('/specs/:format')
}
$routes[] = $route;
$model = $response->getModel($route->getLabel('sdk.response.model', 'none'));
if($model) {
$models[$model->getType()] = $model;
}
$modelLabel = $route->getLabel('sdk.response.model', 'none');
$model = \is_array($modelLabel) ? \array_map(function($m) use($response) {
return $response->getModel($m);
}, $modelLabel) : $response->getModel($modelLabel);
}
}

View file

@ -17,13 +17,13 @@ ini_set('display_startup_errors', 1);
ini_set('default_socket_timeout', -1);
error_reporting(E_ALL);
use Appwrite\Extend\PDO;
use Ahc\Jwt\JWT;
use Ahc\Jwt\JWTException;
use Appwrite\Auth\Auth;
use Appwrite\Database\Database;
use Appwrite\Database\Database as DatabaseOld;
use Appwrite\Database\Adapter\MySQL as MySQLAdapter;
use Appwrite\Database\Adapter\Redis as RedisAdapter;
use Appwrite\Database\Document;
use Appwrite\Event\Event;
use Appwrite\Network\Validator\Email;
use Appwrite\Network\Validator\IP;
@ -40,8 +40,8 @@ use PHPMailer\PHPMailer\PHPMailer;
use Utopia\Cache\Adapter\Redis as RedisCache;
use Utopia\Cache\Cache;
use Utopia\Database\Adapter\MariaDB;
use Utopia\Database\Document as Document2;
use Utopia\Database\Database as Database2;
use Utopia\Database\Document;
use Utopia\Database\Database;
use Utopia\Database\Validator\Structure;
use Utopia\Database\Validator\Authorization;
use Utopia\Validator\Range;
@ -49,6 +49,7 @@ use Swoole\Database\PDOConfig;
use Swoole\Database\PDOPool;
use Swoole\Database\RedisConfig;
use Swoole\Database\RedisPool;
use Utopia\Database\Query;
const APP_NAME = 'Appwrite';
const APP_DOMAIN = 'appwrite.io';
@ -61,7 +62,12 @@ const APP_PAGING_LIMIT = 12;
const APP_LIMIT_COUNT = 5000;
const APP_LIMIT_USERS = 10000;
const APP_CACHE_BUSTER = 151;
const APP_VERSION_STABLE = '0.10.0';
const APP_VERSION_STABLE = '0.9.4';
const APP_DATABASE_ATTRIBUTE_EMAIL = 'email';
const APP_DATABASE_ATTRIBUTE_IP = 'ip';
const APP_DATABASE_ATTRIBUTE_URL = 'url';
const APP_DATABASE_ATTRIBUTE_INT_RANGE = 'intRange';
const APP_DATABASE_ATTRIBUTE_FLOAT_RANGE = 'floatRange';
const APP_STORAGE_UPLOADS = '/storage/uploads';
const APP_STORAGE_FUNCTIONS = '/storage/functions';
const APP_STORAGE_CACHE = '/storage/cache';
@ -88,6 +94,7 @@ const DELETE_TYPE_EXECUTIONS = 'executions';
const DELETE_TYPE_AUDIT = 'audit';
const DELETE_TYPE_ABUSE = 'abuse';
const DELETE_TYPE_CERTIFICATES = 'certificates';
const DELETE_TYPE_USAGE = 'usage';
// Mail Worker Types
const MAIL_TYPE_VERIFICATION = 'verification';
const MAIL_TYPE_RECOVERY = 'recovery';
@ -138,10 +145,11 @@ if(!empty($user) || !empty($pass)) {
} else {
Resque::setBackend(App::getEnv('_APP_REDIS_HOST', '').':'.App::getEnv('_APP_REDIS_PORT', ''));
}
/**
* DB Filters
* Old DB Filters
*/
Database::addFilter('json',
DatabaseOld::addFilter('json',
function($value) {
if(!is_array($value)) {
return $value;
@ -153,7 +161,7 @@ Database::addFilter('json',
}
);
Database::addFilter('encrypt',
DatabaseOld::addFilter('encrypt',
function($value) {
$key = App::getEnv('_APP_OPENSSL_KEY_V1');
$iv = OpenSSL::randomPseudoBytes(OpenSSL::cipherIVLength(OpenSSL::CIPHER_AES_128_GCM));
@ -175,7 +183,68 @@ Database::addFilter('encrypt',
}
);
Database2::addFilter('encrypt',
/**
* New DB Filters
*/
Database::addFilter('casting',
function($value) {
return json_encode(['value' => $value]);
},
function($value) {
if (is_null($value)) {
return null;
}
return json_decode($value, true)['value'];
}
);
Database::addFilter('range',
function($value, Document $attribute) {
if ($attribute->isSet('min')) {
$attribute->removeAttribute('min');
}
if ($attribute->isSet('max')) {
$attribute->removeAttribute('max');
}
return $value;
},
function($value, Document $attribute) {
$formatOptions = json_decode($attribute->getAttribute('formatOptions', []), true);
if (isset($formatOptions['min']) || isset($formatOptions['max'])) {
$attribute
->setAttribute('min', $formatOptions['min'])
->setAttribute('max', $formatOptions['max'])
;
}
return $value;
}
);
Database::addFilter('subQueryAttributes',
function($value) {
return null;
},
function($value, Document $document, Database $database) {
return $database
->find('attributes', [
new Query('collectionId', Query::TYPE_EQUAL, [$document->getId()])
], 100, 0, []);
}
);
Database::addFilter('subQueryIndexes',
function($value) {
return null;
},
function($value, Document $document, Database $database) {
return $database
->find('indexes', [
new Query('collectionId', Query::TYPE_EQUAL, [$document->getId()])
], 100, 0, []);
}
);
Database::addFilter('encrypt',
function($value) {
$key = App::getEnv('_APP_OPENSSL_KEY_V1');
$iv = OpenSSL::randomPseudoBytes(OpenSSL::cipherIVLength(OpenSSL::CIPHER_AES_128_GCM));
@ -196,37 +265,32 @@ Database2::addFilter('encrypt',
}
);
Structure::addFormat('email', function() {
/**
* DB Formats
*/
Structure::addFormat(APP_DATABASE_ATTRIBUTE_EMAIL, function() {
return new Email();
}, Database2::VAR_STRING);
}, Database::VAR_STRING);
Structure::addFormat('ip', function() {
Structure::addFormat(APP_DATABASE_ATTRIBUTE_IP, function() {
return new IP();
}, Database2::VAR_STRING);
}, Database::VAR_STRING);
Structure::addFormat('url', function() {
Structure::addFormat(APP_DATABASE_ATTRIBUTE_URL, function() {
return new URL();
}, Database2::VAR_STRING);
}, Database::VAR_STRING);
Structure::addFormat('int-range', function($attribute) {
// Format encoded as json string containing name and relevant options
// E.g. Range: $format = json_encode(['name'=>$name, 'min'=>$min, 'max'=>$max]);
$format = json_decode($attribute['format'], true);
$min = $format['min'] ?? -INF;
$max = $format['max'] ?? INF;
$type = $attribute['type'];
return new Range($min, $max, $type);
}, Database2::VAR_INTEGER);
Structure::addFormat(APP_DATABASE_ATTRIBUTE_INT_RANGE, function($attribute) {
$min = $attribute['formatOptions']['min'] ?? -INF;
$max = $attribute['formatOptions']['max'] ?? INF;
return new Range($min, $max, Range::TYPE_INTEGER);
}, Database::VAR_INTEGER);
Structure::addFormat('float-range', function($attribute) {
// Format encoded as json string containing name and relevant options
// E.g. Range: $format = json_encode(['name'=>$name, 'min'=>$min, 'max'=>$max]);
$format = json_decode($attribute['format'], true);
$min = $format['min'] ?? -INF;
$max = $format['max'] ?? INF;
$type = $attribute['type'] ?? '';
return new Range($min, $max, $type);
}, Database2::VAR_FLOAT);
Structure::addFormat(APP_DATABASE_ATTRIBUTE_FLOAT_RANGE, function($attribute) {
$min = $attribute['formatOptions']['min'] ?? -INF;
$max = $attribute['formatOptions']['max'] ?? INF;
return new Range($min, $max, Range::TYPE_FLOAT);
}, Database::VAR_FLOAT);
/*
* Registry
@ -238,7 +302,6 @@ $register->set('dbPool', function () { // Register DB connection
$dbPass = App::getEnv('_APP_DB_PASS', '');
$dbScheme = App::getEnv('_APP_DB_SCHEMA', '');
$pool = new PDOPool((new PDOConfig())
->withHost($dbHost)
->withPort($dbPort)
@ -246,6 +309,9 @@ $register->set('dbPool', function () { // Register DB connection
->withCharset('utf8mb4')
->withUsername($dbUser)
->withPassword($dbPass)
->withOptions([
PDO::ATTR_ERRMODE => App::isDevelopment() ? PDO::ERRMODE_WARNING : PDO::ERRMODE_SILENT, // If in production mode, warnings are not displayed
])
, 16);
return $pool;
@ -292,7 +358,6 @@ $register->set('statsd', function () { // Register DB connection
return $statsd;
});
$register->set('smtp', function () {
$mail = new PHPMailer(true);
@ -324,6 +389,29 @@ $register->set('smtp', function () {
$register->set('geodb', function () {
return new Reader(__DIR__.'/db/DBIP/dbip-country-lite-2021-06.mmdb');
});
$register->set('db', function () { // This is usually for our workers or CLI commands scope
$dbHost = App::getEnv('_APP_DB_HOST', '');
$dbUser = App::getEnv('_APP_DB_USER', '');
$dbPass = App::getEnv('_APP_DB_PASS', '');
$dbScheme = App::getEnv('_APP_DB_SCHEMA', '');
$pdo = new PDO("mysql:host={$dbHost};dbname={$dbScheme};charset=utf8mb4", $dbUser, $dbPass, array(
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8mb4',
PDO::ATTR_TIMEOUT => 3, // Seconds
PDO::ATTR_PERSISTENT => true,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
));
return $pdo;
});
$register->set('cache', function () { // This is usually for our workers or CLI commands scope
$redis = new Redis();
$redis->pconnect(App::getEnv('_APP_REDIS_HOST', ''), App::getEnv('_APP_REDIS_PORT', ''));
$redis->setOption(Redis::OPT_READ_TIMEOUT, -1);
return $redis;
});
/*
* Localization
@ -440,8 +528,8 @@ App::setResource('database', function($register) {
// Test Mock
App::setResource('clients', function($request, $console, $project) {
$console->setAttribute('platforms', [ // Allways allow current host
'$collection' => Database::SYSTEM_COLLECTION_PLATFORMS,
$console->setAttribute('platforms', [ // Always allow current host
'$collection' => 'platforms',
'name' => 'Current Host',
'type' => 'web',
'hostname' => $request->getHostname(),
@ -509,7 +597,7 @@ App::setResource('user', function($mode, $project, $console, $request, $response
if (APP_MODE_ADMIN !== $mode) {
if ($project->isEmpty()) {
$user = new Document2(['$id' => '', '$collection' => 'users']);
$user = new Document(['$id' => '', '$collection' => 'users']);
}
else {
$user = $dbForInternal->getDocument('users', Auth::$unique);
@ -521,14 +609,14 @@ App::setResource('user', function($mode, $project, $console, $request, $response
if ($user->isEmpty() // Check a document has been found in the DB
|| !Auth::sessionVerify($user->getAttribute('sessions', []), Auth::$secret)) { // Validate user has valid login token
$user = new Document2(['$id' => '', '$collection' => 'users']);
$user = new Document(['$id' => '', '$collection' => 'users']);
}
if (APP_MODE_ADMIN === $mode) {
if ($user->find('teamId', $project->getAttribute('teamId'), 'memberships')) {
Authorization::setDefaultStatus(false); // Cancel security segmentation for admin users.
} else {
$user = new Document2(['$id' => '', '$collection' => 'users']);
$user = new Document(['$id' => '', '$collection' => 'users']);
}
}
@ -551,7 +639,7 @@ App::setResource('user', function($mode, $project, $console, $request, $response
}
if (empty($user->find('$id', $jwtSessionId, 'sessions'))) { // Match JWT to active token
$user = new Document2(['$id' => '', '$collection' => 'users']);
$user = new Document(['$id' => '', '$collection' => 'users']);
}
}
@ -580,7 +668,7 @@ App::setResource('project', function($dbForConsole, $request, $console) {
}, ['dbForConsole', 'request', 'console']);
App::setResource('console', function() {
return new Document2([
return new Document([
'$id' => 'console',
'name' => 'Appwrite',
'$collection' => 'projects',
@ -591,19 +679,19 @@ App::setResource('console', function() {
'keys' => [],
'platforms' => [
[
'$collection' => Database::SYSTEM_COLLECTION_PLATFORMS,
'$collection' => 'platforms',
'name' => 'Production',
'type' => 'web',
'hostname' => 'appwrite.io',
],
[
'$collection' => Database::SYSTEM_COLLECTION_PLATFORMS,
'$collection' => 'platforms',
'name' => 'Development',
'type' => 'web',
'hostname' => 'appwrite.test',
],
[
'$collection' => Database::SYSTEM_COLLECTION_PLATFORMS,
'$collection' => 'platforms',
'name' => 'Localhost',
'type' => 'web',
'hostname' => 'localhost',
@ -624,7 +712,7 @@ App::setResource('console', function() {
}, []);
App::setResource('consoleDB', function($db, $cache) {
$consoleDB = new Database();
$consoleDB = new DatabaseOld();
$consoleDB->setAdapter(new RedisAdapter(new MySQLAdapter($db, $cache), $cache));
$consoleDB->setNamespace('app_console'); // Should be replaced with param if we want to have parent projects
$consoleDB->setMocks(Config::getParam('collections', []));
@ -633,7 +721,7 @@ App::setResource('consoleDB', function($db, $cache) {
}, ['db', 'cache']);
App::setResource('projectDB', function($db, $cache, $project) {
$projectDB = new Database();
$projectDB = new DatabaseOld();
$projectDB->setAdapter(new RedisAdapter(new MySQLAdapter($db, $cache), $cache));
$projectDB->setNamespace('app_'.$project->getId());
$projectDB->setMocks(Config::getParam('collections', []));
@ -644,7 +732,7 @@ App::setResource('projectDB', function($db, $cache, $project) {
App::setResource('dbForInternal', function($db, $cache, $project) {
$cache = new Cache(new RedisCache($cache));
$database = new Database2(new MariaDB($db), $cache);
$database = new Database(new MariaDB($db), $cache);
$database->setNamespace('project_'.$project->getId().'_internal');
return $database;
@ -653,7 +741,7 @@ App::setResource('dbForInternal', function($db, $cache, $project) {
App::setResource('dbForExternal', function($db, $cache, $project) {
$cache = new Cache(new RedisCache($cache));
$database = new Database2(new MariaDB($db), $cache);
$database = new Database(new MariaDB($db), $cache);
$database->setNamespace('project_'.$project->getId().'_external');
return $database;
@ -662,7 +750,7 @@ App::setResource('dbForExternal', function($db, $cache, $project) {
App::setResource('dbForConsole', function($db, $cache) {
$cache = new Cache(new RedisCache($cache));
$database = new Database2(new MariaDB($db), $cache);
$database = new Database(new MariaDB($db), $cache);
$database->setNamespace('project_console_internal');
return $database;

View file

@ -39,17 +39,29 @@ $cli
]);
}
function notifyDeleteUsageStats(int $interval30m, int $interval1d)
{
Resque::enqueue(Event::DELETE_QUEUE_NAME, Event::DELETE_CLASS_NAME, [
'type' => DELETE_TYPE_USAGE,
'timestamp1d' => time() - $interval1d,
'timestamp30m' => time() - $interval30m,
]);
}
// # of days in seconds (1 day = 86400s)
$interval = (int) App::getEnv('_APP_MAINTENANCE_INTERVAL', '86400');
$executionLogsRetention = (int) App::getEnv('_APP_MAINTENANCE_RETENTION_EXECUTION', '1209600');
$auditLogRetention = (int) App::getEnv('_APP_MAINTENANCE_RETENTION_AUDIT', '1209600');
$abuseLogsRetention = (int) App::getEnv('_APP_MAINTENANCE_RETENTION_ABUSE', '86400');
$usageStatsRetention30m = (int) App::getEnv('_APP_MAINTENANCE_RETENTION_USAGE_30M', '129600');//36 hours
$usageStatsRetention1d = (int) App::getEnv('_APP_MAINTENANCE_RETENTION_USAGE_1D', '8640000'); // 100 days
Console::loop(function() use ($interval, $executionLogsRetention, $abuseLogsRetention, $auditLogRetention){
Console::loop(function() use ($interval, $executionLogsRetention, $abuseLogsRetention, $auditLogRetention, $usageStatsRetention30m, $usageStatsRetention1d) {
$time = date('d-m-Y H:i:s', time());
Console::info("[{$time}] Notifying deletes workers every {$interval} seconds");
notifyDeleteExecutionLogs($executionLogsRetention);
notifyDeleteAbuseLogs($abuseLogsRetention);
notifyDeleteAuditLogs($auditLogRetention);
notifyDeleteUsageStats($usageStatsRetention30m, $usageStatsRetention1d);
}, $interval);
});

586
app/tasks/usage.php Normal file
View file

@ -0,0 +1,586 @@
<?php
global $cli, $register;
require_once __DIR__ . '/../init.php';
use Utopia\App;
use Utopia\Cache\Adapter\Redis;
use Utopia\Cache\Cache;
use Utopia\CLI\Console;
use Utopia\Database\Adapter\MariaDB;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Validator\Authorization;
/**
* Metrics We collect
*
* General
*
* requests
* network
* executions
*
* Database
*
* database.collections.create
* database.collections.read
* database.collections.update
* database.collections.delete
* database.documents.create
* database.documents.read
* database.documents.update
* database.documents.delete
* database.collections.{collectionId}.documents.create
* database.collections.{collectionId}.documents.read
* database.collections.{collectionId}.documents.update
* database.collections.{collectionId}.documents.delete
*
* Storage
*
* storage.buckets.{bucketId}.files.create
* storage.buckets.{bucketId}.files.read
* storage.buckets.{bucketId}.files.update
* storage.buckets.{bucketId}.files.delete
*
* Users
*
* users.create
* users.read
* users.update
* users.delete
* users.sessions.create
* users.sessions.{provider}.create
* users.sessions.delete
*
* Functions
*
* functions.{functionId}.executions
* functions.{functionId}.failures
* functions.{functionId}.compute
*
* Counters
*
* users.count
* storage.files.count
* database.collections.count
* database.documents.count
* database.collections.{collectionId}.documents.count
*
* Totals
*
* storage.total
*
*/
$cli
->task('usage')
->desc('Schedules syncing data from influxdb to Appwrite console db')
->action(function () use ($register) {
Console::title('Usage Aggregation V1');
Console::success(APP_NAME . ' usage aggregation process v1 has started');
$interval = (int) App::getEnv('_APP_USAGE_AGGREGATION_INTERVAL', '30'); // 30 seconds (by default)
$periods = [
[
'key' => '30m',
'startTime' => '-24 hours',
],
[
'key' => '1d',
'startTime' => '-90 days',
],
];
// all the metrics that we are collecting at the moment
$globalMetrics = [
'requests' => [
'table' => 'appwrite_usage_requests_all',
],
'network' => [
'table' => 'appwrite_usage_network_all',
],
'executions' => [
'table' => 'appwrite_usage_executions_all',
],
'database.collections.create' => [
'table' => 'appwrite_usage_database_collections_create',
],
'database.collections.read' => [
'table' => 'appwrite_usage_database_collections_read',
],
'database.collections.update' => [
'table' => 'appwrite_usage_database_collections_update',
],
'database.collections.delete' => [
'table' => 'appwrite_usage_database_collections_delete',
],
'database.documents.create' => [
'table' => 'appwrite_usage_database_documents_create',
],
'database.documents.read' => [
'table' => 'appwrite_usage_database_documents_read',
],
'database.documents.update' => [
'table' => 'appwrite_usage_database_documents_update',
],
'database.documents.delete' => [
'table' => 'appwrite_usage_database_documents_delete',
],
'database.collections.collectionId.documents.create' => [
'table' => 'appwrite_usage_database_documents_create',
'groupBy' => 'collectionId',
],
'database.collections.collectionId.documents.read' => [
'table' => 'appwrite_usage_database_documents_read',
'groupBy' => 'collectionId',
],
'database.collections.collectionId.documents.update' => [
'table' => 'appwrite_usage_database_documents_update',
'groupBy' => 'collectionId',
],
'database.collections.collectionId.documents.delete' => [
'table' => 'appwrite_usage_database_documents_delete',
'groupBy' => 'collectionId',
],
'storage.buckets.bucketId.files.create' => [
'table' => 'appwrite_usage_storage_files_create',
'groupBy' => 'bucketId',
],
'storage.buckets.bucketId.files.read' => [
'table' => 'appwrite_usage_storage_files_read',
'groupBy' => 'bucketId',
],
'storage.buckets.bucketId.files.update' => [
'table' => 'appwrite_usage_storage_files_update',
'groupBy' => 'bucketId',
],
'storage.buckets.bucketId.files.delete' => [
'table' => 'appwrite_usage_storage_files_delete',
'groupBy' => 'bucketId',
],
'users.create' => [
'table' => 'appwrite_usage_users_create',
],
'users.read' => [
'table' => 'appwrite_usage_users_read',
],
'users.update' => [
'table' => 'appwrite_usage_users_update',
],
'users.delete' => [
'table' => 'appwrite_usage_users_delete',
],
'users.sessions.create' => [
'table' => 'appwrite_usage_users_sessions_create',
],
'users.sessions.provider.create' => [
'table' => 'appwrite_usage_users_sessions_create',
'groupBy' => 'provider',
],
'users.sessions.delete' => [
'table' => 'appwrite_usage_users_sessions_delete',
],
'functions.functionId.executions' => [
'table' => 'appwrite_usage_executions_all',
'groupBy' => 'functionId',
],
'functions.functionId.compute' => [
'table' => 'appwrite_usage_executions_time',
'groupBy' => 'functionId',
],
'functions.functionId.failures' => [
'table' => 'appwrite_usage_executions_all',
'groupBy' => 'functionId',
'filters' => [
'functionStatus' => 'failed',
],
],
];
// TODO Maybe move this to the setResource method, and reuse in the http.php file
$attempts = 0;
$max = 10;
$sleep = 1;
do { // connect to db
try {
$attempts++;
$db = $register->get('db');
$redis = $register->get('cache');
break; // leave the do-while if successful
} catch (\Exception $e) {
Console::warning("Database not ready. Retrying connection ({$attempts})...");
if ($attempts >= $max) {
throw new \Exception('Failed to connect to database: ' . $e->getMessage());
}
sleep($sleep);
}
} while ($attempts < $max);
// TODO use inject
$cacheAdapter = new Cache(new Redis($redis));
$dbForProject = new Database(new MariaDB($db), $cacheAdapter);
$dbForConsole = new Database(new MariaDB($db), $cacheAdapter);
$dbForConsole->setNamespace('project_console_internal');
$latestTime = [];
Authorization::disable();
$iterations = 0;
Console::loop(function () use ($interval, $register, $dbForProject, $dbForConsole, $globalMetrics, $periods, &$latestTime, &$iterations) {
$now = date('d-m-Y H:i:s', time());
Console::info("[{$now}] Aggregating usage data every {$interval} seconds");
$loopStart = microtime(true);
/**
* Aggregate InfluxDB every 30 seconds
* @var InfluxDB\Client $client
*/
$client = $register->get('influxdb');
if ($client) {
$attempts = 0;
$max = 10;
$sleep = 1;
$database = $client->selectDB('telegraf');
do { // check if telegraf database is ready
$attempts++;
if(!in_array('telegraf', $client->listDatabases())) {
Console::warning("InfluxDB not ready. Retrying connection ({$attempts})...");
if($attempts >= $max) {
throw new \Exception('InfluxDB database not ready yet');
}
sleep($sleep);
} else {
break; // leave the do-while if successful
}
} while ($attempts < $max);
// sync data
foreach ($globalMetrics as $metric => $options) { //for each metrics
foreach ($periods as $period) { // aggregate data for each period
$start = DateTime::createFromFormat('U', \strtotime($period['startTime']))->format(DateTime::RFC3339);
if (!empty($latestTime[$metric][$period['key']])) {
$start = DateTime::createFromFormat('U', $latestTime[$metric][$period['key']])->format(DateTime::RFC3339);
}
$end = DateTime::createFromFormat('U', \strtotime('now'))->format(DateTime::RFC3339);
$table = $options['table']; //Which influxdb table to query for this metric
$groupBy = empty($options['groupBy']) ? '' : ', "' . $options['groupBy'] . '"'; //Some sub level metrics may be grouped by other tags like collectionId, bucketId, etc
$filters = $options['filters'] ?? []; // Some metrics might have additional filters, like function's status
if (!empty($filters)) {
$filters = ' AND ' . implode(' AND ', array_map(function ($filter, $value) {
return '"' . $filter . '"=\'' . $value . '\'';
}, array_keys($filters), array_values($filters)));
}
$result = $database->query('SELECT sum(value) AS "value" FROM "' . $table . '" WHERE time > \'' . $start . '\' AND time < \'' . $end . '\' AND "metric_type"=\'counter\'' . (empty($filters) ? '' : $filters) . ' GROUP BY time(' . $period['key'] . '), "projectId"' . $groupBy . ' FILL(null)');
$points = $result->getPoints();
foreach ($points as $point) {
$projectId = $point['projectId'];
if (!empty($projectId) && $projectId != 'console') {
$dbForProject->setNamespace('project_' . $projectId . '_internal');
$metricUpdated = $metric;
if (!empty($groupBy)) {
$groupedBy = $point[$options['groupBy']] ?? '';
if (empty($groupedBy)) {
continue;
}
$metricUpdated = str_replace($options['groupBy'], $groupedBy, $metric);
}
$time = \strtotime($point['time']);
$id = \md5($time . '_' . $period['key'] . '_' . $metricUpdated); //Construct unique id for each metric using time, period and metric
$value = (!empty($point['value'])) ? $point['value'] : 0;
try {
$document = $dbForProject->getDocument('stats', $id);
if ($document->isEmpty()) {
$dbForProject->createDocument('stats', new Document([
'$id' => $id,
'period' => $period['key'],
'time' => $time,
'metric' => $metricUpdated,
'value' => $value,
'type' => 0,
]));
} else {
$dbForProject->updateDocument('stats', $document->getId(),
$document->setAttribute('value', $value));
}
$latestTime[$metric][$period['key']] = $time;
} catch (\Exception $e) { // if projects are deleted this might fail
Console::warning("Failed to save data for project {$projectId} and metric {$metricUpdated}: {$e->getMessage()}");
}
}
}
}
}
}
/**
* Aggregate MariaDB every 15 minutes
* Some of the queries here might contain full-table scans.
*/
if ($iterations % 30 == 0) { // Every 15 minutes aggregate number of objects in database
$latestProject = null;
do { // Loop over all the projects
$attempts = 0;
$max = 10;
$sleep = 1;
do { // list projects
try {
$attempts++;
$projects = $dbForConsole->find('projects', [], 100, cursor:$latestProject);
break; // leave the do-while if successful
} catch (\Exception $e) {
Console::warning("Console DB not ready yet. Retrying ({$attempts})...");
if ($attempts >= $max) {
throw new \Exception('Failed access console db: ' . $e->getMessage());
}
sleep($sleep);
}
} while ($attempts < $max);
if (empty($projects)) {
continue;
}
$latestProject = $projects[array_key_last($projects)];
foreach ($projects as $project) {
$projectId = $project->getId();
// Get total storage
$dbForProject->setNamespace('project_' . $projectId . '_internal');
$storageTotal = $dbForProject->sum('files', 'sizeOriginal') + $dbForProject->sum('tags', 'size');
$time = (int) (floor(time() / 1800) * 1800); // Time rounded to nearest 30 minutes
$id = \md5($time . '_30m_storage.total'); //Construct unique id for each metric using time, period and metric
$document = $dbForProject->getDocument('stats', $id);
if ($document->isEmpty()) {
$dbForProject->createDocument('stats', new Document([
'$id' => $id,
'period' => '30m',
'time' => $time,
'metric' => 'storage.total',
'value' => $storageTotal,
'type' => 1,
]));
} else {
$dbForProject->updateDocument('stats', $document->getId(),
$document->setAttribute('value', $storageTotal));
}
$time = (int) (floor(time() / 86400) * 86400); // Time rounded to nearest day
$id = \md5($time . '_1d_storage.total'); //Construct unique id for each metric using time, period and metric
$document = $dbForProject->getDocument('stats', $id);
if ($document->isEmpty()) {
$dbForProject->createDocument('stats', new Document([
'$id' => $id,
'period' => '1d',
'time' => $time,
'metric' => 'storage.total',
'value' => $storageTotal,
'type' => 1,
]));
} else {
$dbForProject->updateDocument('stats', $document->getId(),
$document->setAttribute('value', $storageTotal));
}
$collections = [
'users' => [
'namespace' => 'internal',
],
'collections' => [
'metricPrefix' => 'database',
'namespace' => 'internal',
'subCollections' => [ // Some collections, like collections and later buckets have child collections that need counting
'documents' => [
'namespace' => 'external',
],
],
],
'files' => [
'metricPrefix' => 'storage',
'namespace' => 'internal',
],
];
foreach ($collections as $collection => $options) {
try {
$dbForProject->setNamespace("project_{$projectId}_{$options['namespace']}");
$count = $dbForProject->count($collection);
$dbForProject->setNamespace("project_{$projectId}_internal");
$metricPrefix = $options['metricPrefix'] ?? '';
$metric = empty($metricPrefix) ? "{$collection}.count" : "{$metricPrefix}.{$collection}.count";
$time = (int) (floor(time() / 1800) * 1800); // Time rounded to nearest 30 minutes
$id = \md5($time . '_30m_' . $metric); //Construct unique id for each metric using time, period and metric
$document = $dbForProject->getDocument('stats', $id);
if ($document->isEmpty()) {
$dbForProject->createDocument('stats', new Document([
'$id' => $id,
'time' => $time,
'period' => '30m',
'metric' => $metric,
'value' => $count,
'type' => 1,
]));
} else {
$dbForProject->updateDocument('stats', $document->getId(),
$document->setAttribute('value', $count));
}
$time = (int) (floor(time() / 86400) * 86400); // Time rounded to nearest day
$id = \md5($time . '_1d_' . $metric); //Construct unique id for each metric using time, period and metric
$document = $dbForProject->getDocument('stats', $id);
if ($document->isEmpty()) {
$dbForProject->createDocument('stats', new Document([
'$id' => $id,
'time' => $time,
'period' => '1d',
'metric' => $metric,
'value' => $count,
'type' => 1,
]));
} else {
$dbForProject->updateDocument('stats', $document->getId(),
$document->setAttribute('value', $count));
}
$subCollections = $options['subCollections'] ?? [];
if (empty($subCollections)) {
continue;
}
$latestParent = null;
$subCollectionCounts = []; //total project level count of sub collections
do { // Loop over all the parent collection document for each sub collection
$dbForProject->setNamespace("project_{$projectId}_{$options['namespace']}");
$parents = $dbForProject->find($collection, [], 100, cursor:$latestParent); // Get all the parents for the sub collections for example for documents, this will get all the collections
if (empty($parents)) {
continue;
}
$latestParent = $parents[array_key_last($parents)];
foreach ($parents as $parent) {
foreach ($subCollections as $subCollection => $subOptions) { // Sub collection counts, like database.collections.collectionId.documents.count
$dbForProject->setNamespace("project_{$projectId}_{$subOptions['namespace']}");
$count = $dbForProject->count($parent->getId());
$subCollectionCounts[$subCollection] = ($subCollectionCounts[$subCollection] ?? 0) + $count; // Project level counts for sub collections like database.documents.count
$dbForProject->setNamespace("project_{$projectId}_internal");
$metric = empty($metricPrefix) ? "{$collection}.{$parent->getId()}.{$subCollection}.count" : "{$metricPrefix}.{$collection}.{$parent->getId()}.{$subCollection}.count";
$time = (int) (floor(time() / 1800) * 1800); // Time rounded to nearest 30 minutes
$id = \md5($time . '_30m_' . $metric); //Construct unique id for each metric using time, period and metric
$document = $dbForProject->getDocument('stats', $id);
if ($document->isEmpty()) {
$dbForProject->createDocument('stats', new Document([
'$id' => $id,
'time' => $time,
'period' => '30m',
'metric' => $metric,
'value' => $count,
'type' => 1,
]));
} else {
$dbForProject->updateDocument('stats', $document->getId(),
$document->setAttribute('value', $count));
}
$time = (int) (floor(time() / 86400) * 86400); // Time rounded to nearest day
$id = \md5($time . '_1d_' . $metric); //Construct unique id for each metric using time, period and metric
$document = $dbForProject->getDocument('stats', $id);
if ($document->isEmpty()) {
$dbForProject->createDocument('stats', new Document([
'$id' => $id,
'time' => $time,
'period' => '1d',
'metric' => $metric,
'value' => $count,
'type' => 1,
]));
} else {
$dbForProject->updateDocument('stats', $document->getId(),
$document->setAttribute('value', $count));
}
}
}
} while (!empty($parents));
/**
* Inserting project level counts for sub collections like database.documents.count
*/
foreach ($subCollectionCounts as $subCollection => $count) {
$dbForProject->setNamespace("project_{$projectId}_internal");
$metric = empty($metricPrefix) ? "{$subCollection}.count" : "{$metricPrefix}.{$subCollection}.count";
$time = (int) (floor(time() / 1800) * 1800); // Time rounded to nearest 30 minutes
$id = \md5($time . '_30m_' . $metric); //Construct unique id for each metric using time, period and metric
$document = $dbForProject->getDocument('stats', $id);
if ($document->isEmpty()) {
$dbForProject->createDocument('stats', new Document([
'$id' => $id,
'time' => $time,
'period' => '30m',
'metric' => $metric,
'value' => $count,
'type' => 1,
]));
} else {
$dbForProject->updateDocument('stats', $document->getId(),
$document->setAttribute('value', $count));
}
$time = (int) (floor(time() / 86400) * 86400); // Time rounded to nearest day
$id = \md5($time . '_1d_' . $metric); //Construct unique id for each metric using time, period and metric
$document = $dbForProject->getDocument('stats', $id);
if ($document->isEmpty()) {
$dbForProject->createDocument('stats', new Document([
'$id' => $id,
'time' => $time,
'period' => '1d',
'metric' => $metric,
'value' => $count,
'type' => 1,
]));
} else {
$dbForProject->updateDocument('stats', $document->getId(),
$document->setAttribute('value', $count));
}
}
} catch (\Exception$e) {
Console::warning("Failed to save database counters data for project {$collection}: {$e->getMessage()}");
}
}
}
} while (!empty($projects));
}
$iterations++;
$loopTook = microtime(true) - $loopStart;
$now = date('d-m-Y H:i:s', time());
Console::info("[{$now}] Aggregation took {$loopTook} seconds");
}, $interval);
});

View file

@ -251,7 +251,7 @@
</span>
<div class="pull-start margin-end avatar-container">
<img onerror="this.onerror=null;this.src='/images/unknown.svg'" data-ls-attrs="src={{env.API}}/avatars/browsers/{{session.clientCode|lowercase}}?width=60&height=60&project={{env.PROJECT}},title={{session.clientName}},alt={{session.clientName}}" class="avatar" loading="lazy" width="60" height="60" />
<img onerror="this.onerror=null;this.className='avatar hide'" data-ls-attrs="src={{env.API}}/avatars/browsers/{{session.clientCode|lowercase}}?width=60&height=60&project={{env.PROJECT}},title={{session.clientName}},alt={{session.clientName}}" class="avatar" loading="lazy" width="60" height="60" />
<div data-ls-if="{{session.provider}} !== 'email'" class="corner">
<img data-ls-attrs="src=/images/users/{{session.provider}}.png?buster=<?php echo APP_CACHE_BUSTER; ?>,title={{session.provider}},alt={{session.provider}}" class="avatar xs" loading="lazy" width="30" height="30" />
@ -266,7 +266,7 @@
</span>
<div class="margin-top-small">
<img onerror="this.onerror=null;this.src='/images/unknown.svg'" data-ls-if="{{session.countryCode}} !== '--'" data-ls-attrs="src={{env.API}}/avatars/flags/{{session.countryCode}}?width=80&height=80&project={{env.PROJECT}}" class="avatar xxs margin-end-small inline" />
<img onerror="this.onerror=null;this.className='avatar hide'" data-ls-if="{{session.countryCode}} !== '--'" data-ls-attrs="src={{env.API}}/avatars/flags/{{session.countryCode}}?width=80&height=80&project={{env.PROJECT}}" class="avatar xxs margin-end-small inline" />
<small data-ls-bind="{{session.ip}}"></small> / <small data-ls-bind="{{session.countryName}}"></small>
</div>
</li>
@ -308,7 +308,7 @@
<th width="120">Date</th>
<th width="175">Event</th>
<th>Client</th>
<th width="90">Location</th>
<th width="110">Location</th>
<th width="90">IP</th>
</tr>
</thead>
@ -317,13 +317,13 @@
<td data-title="Date: "><span data-ls-bind="{{log.time|dateTime}}"></span></td>
<td data-title="Event: "><span data-ls-bind="{{log.event}}"></span></td>
<td data-title="Client: ">
<img onerror="this.onerror=null;this.src='/images/unknown.svg'" data-ls-attrs="src={{env.API}}/avatars/browsers/{{log.clientCode|lowercase}}?width=80&height=80&project={{env.PROJECT}},title={{log.clientName}},alt={{log.clientName}}" class="avatar xxs inline margin-end-small" />
<img onerror="this.onerror=null;this.className='avatar hide'" data-ls-attrs="src={{env.API}}/avatars/browsers/{{log.clientCode|lowercase}}?width=80&height=80&project={{env.PROJECT}},title={{log.clientName}},alt={{log.clientName}}" class="avatar xxs inline margin-end-small" />
<span data-ls-if="(!{{log.clientName}})">Unknown</span>
<span data-ls-if="({{log.clientName}})" data-ls-bind="{{log.clientName}} {{log.clientVersion}} on {{log.model}} {{log.osName}} {{log.osVersion}}"></span>
</td>
<td data-title="Location: ">
<img onerror="this.onerror=null;this.src='/images/unknown.svg'" data-ls-attrs="src={{env.API}}/avatars/flags/{{log.countryCode}}?width=80&height=80&project={{env.PROJECT}}" class="avatar xxs inline margin-end-small" />
<span data-ls-bind="{{log.geo.countryName}}"></span>
<img onerror="this.onerror=null;this.className='avatar xxs hide'" data-ls-attrs="src={{env.API}}/avatars/flags/{{log.countryCode}}?width=80&height=80&project={{env.PROJECT}}" class="avatar xxs inline margin-end-small" />
<span data-ls-bind="{{log.countryName}}"></span>
</td>
<td data-title="IP: "><span data-ls-bind="{{log.ip}}"></span></td>
</tr>

View file

@ -21,7 +21,7 @@ $interval = floor((int)$this->getParam('interval', 0) / 86400);
<th width="120">Date</th>
<th width="180">Initiator</th>
<th>Event</th>
<th width="90">Location</th>
<th width="110">Location</th>
<th width="90">IP</th>
</tr>
</thead>
@ -32,13 +32,15 @@ $interval = floor((int)$this->getParam('interval', 0) / 86400);
<span data-ls-if="{{log.userName|escape}} !== '' && {{log.mode}} === ''"><i class="icon-user"></i>&nbsp; <a data-ls-attrs="href=/console/users/user?id={{log.userId}}&project={{router.params.project}}" data-ls-bind="{{log.userName}}"></a></span>
<span data-ls-if="{{log.userName|escape}} === '' && {{log.userEmail}} !== '' && {{log.mode}} !== 'key'"><i class="icon-user"></i>&nbsp; Unknown</span>
<span data-ls-if="{{log.userName|escape}} === '' && {{log.userEmail}} === '' && {{log.mode}} !== 'key'"><i class="icon-user"></i>&nbsp; Anonymous User</span>
<span data-ls-if="{{log.mode}} === 'admin'"><i class="icon-user"></i>&nbsp; <span data-ls-bind="{{log.userName}} (Admin)"></span></span>
<span data-ls-if="{{log.mode}} === 'admin'">
<img src="" data-ls-attrs="src={{log.userName|avatar}}" data-size="45" alt="User Avatar" class="avatar xxs inline margin-end-small" loading="lazy" width="30" height="30" /> <span data-ls-bind="{{log.userName}}"></span> <span class="text-fade text-size-xs">(Admin)</span>
</span>
<span data-ls-if="{{log.mode}} === 'key'"> <i class="icon-key"></i>&nbsp; API Key</span>
</td>
<td data-title="Event: "><span data-ls-bind="{{log.event}}"></span></td>
<td data-title="Location: ">
<img onerror="this.onerror=null;this.src='/images/unknown.svg'" data-ls-attrs="src={{env.API}}/avatars/flags/{{log.countryCode}}?width=80&height=80&project={{env.PROJECT}}" class="avatar xxs inline margin-end-small" />
<span data-ls-bind="{{log.geo.countryName}}"></span>
<img onerror="this.onerror=null;this.className='avatar xxs hide'" data-ls-attrs="src={{env.API}}/avatars/flags/{{log.countryCode}}?width=80&height=80&project={{env.PROJECT}}" class="avatar xxs inline margin-end-small" />
<span data-ls-bind="{{log.countryName}}"></span>
</td>
<td data-title="IP: "><span data-ls-bind="{{log.ip}}"></span></td>
</tr>

View file

@ -57,7 +57,7 @@ $logs = $this->getParam('logs', null);
</div>
<div data-ls-if="({{project-documents.sum}})" class="margin-top-negative">
<div class="margin-bottom-small margin-end-small text-align-end text-size-small"><span data-ls-bind="{{project-documents.sum}}"></span> documents found</div>
<div class="margin-bottom-small text-align-end text-size-small text-fade"><span data-ls-bind="{{project-documents.sum}}"></span> documents found</div>
<div class="box margin-bottom y-scroll text-size-small">
<table class="vertical borders">
@ -65,14 +65,14 @@ $logs = $this->getParam('logs', null);
<tr data-ls-loop="project-collection.attributes" data-ls-as="attribute" data-ls-prefix="template-attribute-title-first" data-ls-limit="20">
<td style="width: 170px">
<i data-ls-attrs="class=pull-end icon-{{attribute.type}} text-size-xs"></i>
<span class="text-bold" data-ls-bind="{{attribute.$id}}"></span>
<span class="text-bold" data-ls-bind="{{attribute.key}}"></span>
<span class="text-size-small" data-ls-if="{{attribute.array}}">[]</span>
</td>
</tr>
</thead>
<tbody data-ls-loop="project-documents.documents" data-ls-as="node">
<tr data-ls-loop="project-collection.attributes" data-ls-as="attribute" data-ls-prefix="template-attribute-body-first" data-ls-limit="20">
<td data-ls-attrs="data-title={{attribute.$id}}: ">
<td data-ls-attrs="data-title={{attribute.key}}: ">
<!-- <a href="" target="_blank" rel="noopener" class="pull-end margin-start"><i class="icon-link-ext"></i></a> -->
<a data-ls-attrs="href=/console/database/document?id={{node.$id}}&collection={{router.params.id}}&project={{router.params.project}}"><span data-ls-bind="{{node|documentAttribute}}"></span></a>
<span data-ls-if="!{{node|documentAttribute}}" class="text-fade">n/a</span>
@ -130,17 +130,17 @@ $logs = $this->getParam('logs', null);
<h2>Attributes</h2>
<div class="clear box margin-bottom">
<div data-ls-if="0 == {{project-collection.attributes.length}} && 0 == {{project-collection.attributesInQueue.length}}">
<div data-ls-if="0 == {{project-collection.attributes.length}}">
<h3 class="margin-bottom-small text-bold">No Attributes Found</h3>
<p class="margin-bottom-no">Add your first attribute to get started</p>
</div>
<table class="vertical" data-ls-if="0 < {{project-collection.attributes.length}} || 0 < {{project-collection.attributesInQueue.length}}">
<table class="vertical" data-ls-if="0 < {{project-collection.attributes.length}}">
<thead>
<tr>
<th width="30"></th>
<th width="80"></th>
<th width="100"></th>
<th width="130">Attribute ID</th>
<th width="100">Type</th>
<th width="180">Default</th>
@ -154,11 +154,14 @@ $logs = $this->getParam('logs', null);
<i data-ls-attrs="class=icon-{{attribute.type}}"></i>
</td>
<td data-title="Status">
<span class="text-size-small text-success">available&nbsp;</span>
<span data-ls-if="{{attribute.status}} == 'available'" class="text-size-small text-success">available&nbsp;</span>
<span data-ls-if="{{attribute.status}} == 'processing'" class="text-size-small text-info">processing&nbsp;</span>
<span data-ls-if="{{attribute.status}} == 'failed'" class="text-size-small text-danger">failed&nbsp;</span>
<span data-ls-if="{{attribute.status}} == 'deleting'" class="text-size-small text-danger">deleting&nbsp;</span>
</td>
<td data-title="Attribute ID: ">
<span class="text-size-small" data-ls-bind="{{attribute.$id}}"></span><span class="text-size-small" data-ls-if="{{attribute.size}}" data-ls-bind=" ({{attribute.size}})"></span>
<span class="text-size-small" data-ls-bind="{{attribute.key}}"></span><span class="text-size-small" data-ls-if="{{attribute.size}}" data-ls-bind=" ({{attribute.size}})"></span>
</td>
<td data-title="Type:">
@ -176,7 +179,8 @@ $logs = $this->getParam('logs', null);
</td>
<td data-title="">
<form class="pull-end"
<form data-ls-if="{{attribute.status}} !== 'deleting'"
class="pull-end"
data-analytics
data-analytics-activity
data-analytics-event="submit"
@ -195,7 +199,7 @@ $logs = $this->getParam('logs', null);
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="collectionId" data-ls-bind="{{project-collection.$id}}" />
<input type="hidden" name="attributeId" data-ls-bind="{{attribute.$id}}" />
<input type="hidden" name="attributeId" data-ls-bind="{{attribute.key}}" />
<button class="danger small">Delete</button>
</form>
@ -203,54 +207,30 @@ $logs = $this->getParam('logs', null);
</tr>
</tbody>
</table>
<hr data-ls-if="0 < {{project-collection.attributesInQueue.length}}" />
<table class="vertical" data-ls-if="0 < {{project-collection.attributesInQueue.length}}">
<tbody data-ls-loop="project-collection.attributesInQueue" data-ls-as="attribute">
<tr>
<td width="30">
<i data-ls-attrs="class=icon-{{attribute.type}}"></i>
</td>
<td width="80" data-title="Status">
<span class="text-size-small text-info">creating&nbsp;</span>
</td>
<td width="130" data-title="Attribute ID: ">
<span class="text-size-small" data-ls-bind="{{attribute.$id}}"></span><span class="text-size-small" data-ls-if="{{attribute.size}}" data-ls-bind=" ({{attribute.size}})"></span>
</td>
<td width="100" data-title="Type:">
<span class="text-size-small" data-ls-bind="{{attribute.type}}"></span>
<span class="text-size-small" data-ls-if="{{attribute.array}}">[]</span>
</td>
<td width="180" data-title="Default:">
<span class="text-size-small" data-ls-bind="{{attribute.default}}" data-ls-attr="title={{attribute.default}}"></span>
<span class="text-fade text-size-small" data-ls-if="!({{attribute.default}})">n/a</span>
</td>
<td data-title="">
<span class="text-size-small text-danger" data-ls-if="{{attribute.required}}">required</span>
</td>
</tr>
</tbody>
</table>
</div>
<div class="drop-list pull-start" data-ls-ui-open="" data-button-aria="Choose Platform" data-button-text="Add Attribute" data-button-class="button" data-blur="1">
<ul>
<li>
<div class="link new-attribute-string"><img src="/images/clients/web.png?v=<?php echo APP_CACHE_BUSTER; ?>" alt="String Attribute Logo" class="avatar xxs margin-end-small" loading="lazy" /> New String Attribute</div>
<div class="link new-attribute-string"><i class="avatar icon-string"></i> New String Attribute</div>
</li>
<li>
<div class="link new-attribute-integer"><img src="/images/clients/web.png?v=<?php echo APP_CACHE_BUSTER; ?>" alt="String Attribute Logo" class="avatar xxs margin-end-small" loading="lazy" /> New Integer Attribute</div>
<div class="link new-attribute-integer"><i class="avatar icon-integer"></i> New Integer Attribute</div>
</li>
<li>
<div class="link new-attribute-float"><img src="/images/clients/web.png?v=<?php echo APP_CACHE_BUSTER; ?>" alt="String Attribute Logo" class="avatar xxs margin-end-small" loading="lazy" /> New Float Attribute</div>
<div class="link new-attribute-float"><i class="avatar icon-float"></i> New Float Attribute</div>
</li>
<li>
<div class="link new-attribute-boolean"><img src="/images/clients/web.png?v=<?php echo APP_CACHE_BUSTER; ?>" alt="String Attribute Logo" class="avatar xxs margin-end-small" loading="lazy" /> New Boolean Attribute</div>
<div class="link new-attribute-boolean"><i class="avatar icon-boolean"></i> New Boolean Attribute</div>
</li>
<li>
<div class="link new-attribute-url"><i class="avatar icon-link"></i> New URL Attribute</div>
</li>
<li>
<div class="link new-attribute-email"><i class="avatar icon-mail"></i> New Email Attribute</div>
</li>
<li>
<div class="link new-attribute-ip"><i class="avatar icon-ip"></i> New IP Attribute</div>
</li>
</ul>
</div>
@ -259,13 +239,13 @@ $logs = $this->getParam('logs', null);
<h2>Indexes</h2>
<div class="clear box margin-bottom">
<div data-ls-if="0 == {{project-collection.indexes.length}} && 0 == {{project-collection.indexesInQueue.length}}">
<div data-ls-if="0 == {{project-collection.indexes.length}}">
<h3 class="margin-bottom-small text-bold">No Indexes Found</h3>
<p class="margin-bottom-no">Add your first index to get started</p>
</div>
<table class="vertical multi-line" data-ls-if="0 < {{project-collection.indexes.length}} || 0 < {{project-collection.indexesInQueue.length}}">
<table class="vertical multi-line" data-ls-if="0 < {{project-collection.indexes.length}}">
<thead>
<tr>
<th width="30"></th>
@ -284,11 +264,14 @@ $logs = $this->getParam('logs', null);
</td>
<td data-title="Status">
<span class="text-size-small text-success">available</span>
<span data-ls-if="{{index.status}} == 'available'" class="text-size-small text-success">available&nbsp;</span>
<span data-ls-if="{{index.status}} == 'processing'" class="text-size-small text-info">processing&nbsp;</span>
<span data-ls-if="{{index.status}} == 'failed'" class="text-size-small text-danger">failed&nbsp;</span>
<span data-ls-if="{{index.status}} == 'deleting'" class="text-size-small text-danger">deleting&nbsp;</span>
</td>
<td data-title="Index ID: ">
<span class="text-size-small" data-ls-bind="{{index.$id}}"></span><span class="text-size-small" data-ls-if="{{index.size}}" data-ls-bind="({{index.size}})"></span>
<span class="text-size-small" data-ls-bind="{{index.key}}"></span><span class="text-size-small" data-ls-if="{{index.size}}" data-ls-bind="({{index.size}})"></span>
</td>
<td data-title="Type:">
@ -305,7 +288,8 @@ $logs = $this->getParam('logs', null);
</td>
<td data-title="">
<form class="pull-end"
<form data-ls-if="{{index.status}} !== 'deleting'"
class="pull-end"
data-analytics
data-analytics-activity
data-analytics-event="submit"
@ -324,7 +308,7 @@ $logs = $this->getParam('logs', null);
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="collectionId" data-ls-bind="{{project-collection.$id}}" />
<input type="hidden" name="indexId" data-ls-bind="{{index.$id}}" />
<input type="hidden" name="indexId" data-ls-bind="{{index.key}}" />
<button class="danger small">Delete</button>
</form>
@ -332,43 +316,6 @@ $logs = $this->getParam('logs', null);
</tr>
</tbody>
</table>
<hr data-ls-if="0 < {{project-collection.indexesInQueue.length}}" />
<table class="vertical multi-line" data-ls-if="0 < {{project-collection.indexesInQueue.length}}">
<tbody data-ls-loop="project-collection.indexesInQueue" data-ls-as="index">
<tr>
<td width="30">
<i class="icon-key"></i>
</td>
<td width="80" data-title="Status">
<span class="text-size-small text-info">creating</span>
</td>
<td width="130" data-title="Index ID: ">
<span class="text-size-small" data-ls-bind="{{index.$id}}"></span><span class="text-size-small" data-ls-if="{{index.size}}" data-ls-bind="({{index.size}})"></span>
</td>
<td width="100" data-title="Type:">
<span class="text-size-small" data-ls-bind="{{index.type}}"></span>
<span class="text-size-small" data-ls-if="{{index.array}}">[]</span>
</td>
<td width="180" data-title="Attributes:">
<span class="text-size-small" data-ls-bind="{{index|indexAttributes}}"></span>
</td>
<td data-title="">
<span class="text-size-small text-danger" data-ls-if="{{index.required}}">required</span>
</td>
<td width="80" data-title="">
</td>
</tr>
</tbody>
</table>
</div>
<button class="new-index">Add Index</button>
@ -417,7 +364,7 @@ $logs = $this->getParam('logs', null);
<hr class="margin-top-small" />
<div class="row">
<div class="col span-1"><input type="radio" class="margin-top-no" /></div>
<div class="col span-1"><input name="permission" value="document" type="radio" class="margin-top-no" data-ls-bind="{{project-collection.permission}}" /></div>
<div class="col span-11">
<b>Document Level</b>
<p class="text-fade margin-top-tiny">Bla bla bla bla bla Bla blabla bla Bla blabla bla Bla blabla bla Bla blaBla bla bla Bla bla bla Bla bla bla Bla bla bla Bla bla bla Bla bla bla Bla bla bla Bla bla bla</p>
@ -425,7 +372,7 @@ $logs = $this->getParam('logs', null);
</div>
<div class="row">
<div class="col span-1"><input type="radio" class="margin-top-tiny" /></div>
<div class="col span-1"><input name="permission" value="collection" type="radio" class="margin-top-tiny" data-ls-bind="{{project-collection.permission}}" /></div>
<div class="col span-11">
<b>Collection Level</b>
<p class="text-fade margin-top-tiny">Bla bla bla Bla blabla bla Bla blabla bla Bla blabla bla Bla bla bla Bla bla bla Bla bla bla Bla bla bla Bla bla bla Bla bla bla Bla</p>
@ -526,7 +473,7 @@ $logs = $this->getParam('logs', null);
<input id="string-default" name="default" type="text" class="margin-bottom-large" autocomplete="off">
<footer>
<button type="submit">Create</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Back</button>
<button type="submit">Create</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</footer>
</form>
</div>
@ -569,19 +516,107 @@ $logs = $this->getParam('logs', null);
<div class="row responsive thin">
<div class="col span-6 margin-bottom-small">
<label for="integer-min">Min</label>
<input type="text" class="full-width" name="number" value="0" required autocomplete="off" />
<input id="integer-min" type="number" class="full-width" name="min" value="0" required autocomplete="off" data-cast-to="integer" />
</div>
<div class="col span-6 margin-bottom-small">
<label for="integer-min">Max</label>
<input type="text" class="full-width" name="number" value="0" required autocomplete="off" />
<label for="integer-max">Max</label>
<input id="integer-max" type="number" class="full-width" name="max" value="0" required autocomplete="off" data-cast-to="integer" />
</div>
</div>
<label for="integer-default">Default Value</label>
<input id="integer-default" name="default" type="number" value="0" class="margin-bottom-large" autocomplete="off">
<input id="integer-default" name="default" type="number" value="0" class="margin-bottom-large" autocomplete="off" data-cast-to="integer">
<footer>
<button type="submit">Create</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Back</button>
<button type="submit">Create</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</footer>
</form>
</div>
<div data-ui-modal class="modal box close sticky-footer" data-button-alias=".new-attribute-email">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
<h1>Add Email Attribute</h1>
<form
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Collection Attribute (email)"
data-service="database.createEmailAttribute"
data-scope="sdk"
data-event="submit"
data-success="alert,trigger,reset"
data-success-param-alert-text="Created new attribute successfully"
data-success-param-trigger-events="database.createAttribute"
data-failure="alert"
data-failure-param-alert-text="Failed to create attribute"
data-failure-param-alert-classname="error">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="collectionId" data-ls-bind="{{router.params.id}}" />
<label for="string-attributeId">Attribute ID</label>
<input type="text" class="full-width" name="attributeId" required autocomplete="off" maxlength="128" />
<div class="margin-bottom">
<input name="required" type="hidden" data-forms-switch data-cast-to="boolean" /> &nbsp; Required <span class="tooltip" data-tooltip="Mark whether this is a required attribute"><i class="icon-info-circled"></i></span>
</div>
<div class="margin-bottom">
<input name="array" type="hidden" data-forms-switch data-cast-to="boolean" /> &nbsp; Array <span class="tooltip" data-tooltip="Mark whether this attribute should act as an array"><i class="icon-info-circled"></i></span>
</div>
<label for="string-default">Default Value</label>
<input id="string-default" name="default" type="email" placeholder="demo@appwrite.io" class="margin-bottom-large" autocomplete="off">
<footer>
<button type="submit">Create</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</footer>
</form>
</div>
<div data-ui-modal class="modal box close sticky-footer" data-button-alias=".new-attribute-url">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
<h1>Add URL Attribute</h1>
<form
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Collection Attribute (url)"
data-service="database.createUrlAttribute"
data-scope="sdk"
data-event="submit"
data-success="alert,trigger,reset"
data-success-param-alert-text="Created new attribute successfully"
data-success-param-trigger-events="database.createAttribute"
data-failure="alert"
data-failure-param-alert-text="Failed to create attribute"
data-failure-param-alert-classname="error">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="collectionId" data-ls-bind="{{router.params.id}}" />
<label for="string-attributeId">Attribute ID</label>
<input type="text" class="full-width" name="attributeId" required autocomplete="off" maxlength="128" />
<div class="margin-bottom">
<input name="required" type="hidden" data-forms-switch data-cast-to="boolean" /> &nbsp; Required <span class="tooltip" data-tooltip="Mark whether this is a required attribute"><i class="icon-info-circled"></i></span>
</div>
<div class="margin-bottom">
<input name="array" type="hidden" data-forms-switch data-cast-to="boolean" /> &nbsp; Array <span class="tooltip" data-tooltip="Mark whether this attribute should act as an array"><i class="icon-info-circled"></i></span>
</div>
<label for="string-default">Default Value</label>
<input id="string-default" name="default" type="url" placeholder="https://appwrite.io/my-link" class="margin-bottom-large" autocomplete="off">
<footer>
<button type="submit">Create</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</footer>
</form>
</div>
@ -598,7 +633,7 @@ $logs = $this->getParam('logs', null);
<p>Please add your first attribute before adding your first index.</p>
</div>
<button data-ui-modal-close="" type="button" class="reverse">Back</button>
<button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</div>
<form data-ls-if="0 < {{project-collection.attributes.length}}"
@ -639,7 +674,7 @@ $logs = $this->getParam('logs', null);
<div class="row responsive thin margin-bottom-tiny">
<div class="col span-7 margin-bottom-small">
<select data-duplications data-ls-attrs="name=attributes" data-ls-loop="project-collection.attributes" data-ls-as="option" data-cast-to="array" class="margin-bottom-no">
<option data-ls-attrs="value={{option.$id}}" data-ls-bind="{{option.$id}}"></option>
<option data-ls-attrs="value={{option.key}}" data-ls-bind="{{option.key}}"></option>
</select>
</div>
<div class="col span-4 margin-bottom-small">
@ -656,7 +691,7 @@ $logs = $this->getParam('logs', null);
</div>
<footer>
<button type="submit">Create</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Back</button>
<button type="submit">Create</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</footer>
</form>
</div>

View file

@ -71,11 +71,11 @@
<ul data-ls-loop="project-collection.attributes" data-ls-as="attribute">
<li>
<label>
<div data-ls-bind="{{attribute.$id}}" class="margin-bottom-tiny"></div>
<div data-ls-bind="{{attribute.key}}" class="margin-bottom-tiny"></div>
<div data-ls-if="{{attribute.required}}" class="text-size-xs text-danger text-fade">required</div>
<div data-ls-if="!{{attribute.required}}" class="text-size-xs text-fade">optional</div>
</label>
<textarea data-ls-attrs="name={{attribute.$id}}" data-ls-bind="{{project-document|documentAttribute}}"></textarea>
<textarea data-ls-attrs="name={{attribute.key}}" data-ls-bind="{{project-document|documentAttribute}}"></textarea>
</li>
</ul>
</fieldset>

View file

@ -107,9 +107,9 @@
<label for="collection-name">Name</label>
<input type="text" class="full-width" id="collection-name" name="name" required autocomplete="off" maxlength="128" />
<input type="hidden" id="collection-permission" name="permission" required value="document" />
<input type="hidden" id="collection-read" name="read" required data-cast-to="json" value="<?php echo htmlentities(json_encode([])); ?>" />
<input type="hidden" id="collection-write" name="write" required data-cast-to="json" value="<?php echo htmlentities(json_encode([])); ?>" />
<input type="hidden" id="collection-rules" name="rules" required data-cast-to="json" value="{}" />
<hr />

View file

@ -34,7 +34,7 @@ $runtimes = $this->getParam('runtimes', []);
</div>
<div data-ls-if="0 != {{project-functions.functions.length}}">
<div class="margin-bottom-small margin-end-small text-align-end text-size-small margin-top-negative"><span data-ls-bind="{{project-functions.sum}}"></span> functions found</div>
<div class="margin-bottom-small text-align-end text-size-small margin-top-negative text-fade"><span data-ls-bind="{{project-functions.sum}}"></span> functions found</div>
<div class="box margin-bottom">
<ul data-ls-loop="project-functions.functions" data-ls-as="function" class="list">

View file

@ -129,7 +129,7 @@ $usageStatsEnabled = $this->getParam('usageStatsEnabled', true);
</div>
<div class="col span-3">
<div class="value"><span class="sum" data-ls-bind="{{usage.functions.total|statsTotal}}" data-default="0">0</span></div>
<div class="margin-top-small"><b class="text-size-small unit">Func. Executions</b></div>
<div class="margin-top-small"><b class="text-size-small unit">Executions</b></div>
</div>
</div>
</div>
@ -290,7 +290,7 @@ $usageStatsEnabled = $this->getParam('usageStatsEnabled', true);
</div>
</div>
<button type="submit">Register</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Back</button>
<button type="submit">Register</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</form>
</div>
@ -322,7 +322,7 @@ $usageStatsEnabled = $this->getParam('usageStatsEnabled', true);
<hr />
<button type="submit">Update</button> &nbsp; <button data-ls-ui-trigger="modal-close" type="button" class="reverse">Back</button>
<button type="submit">Update</button> &nbsp; <button data-ls-ui-trigger="modal-close" type="button" class="reverse">Cancel</button>
</form>
</script>
@ -358,7 +358,7 @@ $usageStatsEnabled = $this->getParam('usageStatsEnabled', true);
<hr />
<button type="submit">Register</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Back</button>
<button type="submit">Register</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</form>
</div>
@ -398,7 +398,7 @@ $usageStatsEnabled = $this->getParam('usageStatsEnabled', true);
<hr />
<button type="submit">Register</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Back</button>
<button type="submit">Register</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</form>
</li>
@ -432,7 +432,7 @@ $usageStatsEnabled = $this->getParam('usageStatsEnabled', true);
<hr />
<button type="submit">Register</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Back</button>
<button type="submit">Register</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</form>
</li>
<li>
@ -465,7 +465,7 @@ $usageStatsEnabled = $this->getParam('usageStatsEnabled', true);
<hr />
<button type="submit">Register</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Back</button>
<button type="submit">Register</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</form>
</li>
<li>
@ -498,7 +498,7 @@ $usageStatsEnabled = $this->getParam('usageStatsEnabled', true);
<hr />
<button type="submit">Register</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Back</button>
<button type="submit">Register</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</form>
</li>
<li>
@ -531,7 +531,7 @@ $usageStatsEnabled = $this->getParam('usageStatsEnabled', true);
<hr />
<button type="submit">Register</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Back</button>
<button type="submit">Register</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</form>
</li>
</ul>
@ -565,7 +565,7 @@ $usageStatsEnabled = $this->getParam('usageStatsEnabled', true);
<hr />
<button type="submit">Update</button> &nbsp; <button data-ls-ui-trigger="modal-close" type="button" class="reverse">Back</button>
<button type="submit">Update</button> &nbsp; <button data-ls-ui-trigger="modal-close" type="button" class="reverse">Cancel</button>
</form>
</script>
@ -596,7 +596,7 @@ $usageStatsEnabled = $this->getParam('usageStatsEnabled', true);
<hr />
<button type="submit">Update</button> &nbsp; <button data-ls-ui-trigger="modal-close" type="button" class="reverse">Back</button>
<button type="submit">Update</button> &nbsp; <button data-ls-ui-trigger="modal-close" type="button" class="reverse">Cancel</button>
</form>
</script>
@ -628,7 +628,7 @@ $usageStatsEnabled = $this->getParam('usageStatsEnabled', true);
<hr />
<button type="submit">Update</button> &nbsp; <button data-ls-ui-trigger="modal-close" type="button" class="reverse">Back</button>
<button type="submit">Update</button> &nbsp; <button data-ls-ui-trigger="modal-close" type="button" class="reverse">Cancel</button>
</form>
</script>

View file

@ -55,7 +55,7 @@ $fileLimitHuman = $this->getParam('fileLimitHuman', 0);
</div>
<div data-ls-if="0 != {{project-files.sum}}">
<div class="margin-bottom-small margin-end-small text-align-end text-size-small"><span data-ls-bind="{{project-files.sum}}"></span> files found</div>
<div class="margin-bottom-small text-align-end text-size-small text-fade"><span data-ls-bind="{{project-files.sum}}"></span> files found</div>
<div class="box margin-bottom">
<table class="vertical">

View file

@ -56,7 +56,7 @@ $auth = $this->getParam('auth', []);
</div>
<div data-ls-if="0 != {{project-users.sum}}">
<div class="margin-bottom-small margin-end-small text-align-end text-size-small"><span data-ls-bind="{{project-users.sum}}"></span> users found</div>
<div class="margin-bottom-small text-align-end text-size-small text-fade"><span data-ls-bind="{{project-users.sum}}"></span> users found</div>
<div class="box margin-bottom">
<table class="vertical">
@ -223,7 +223,7 @@ $auth = $this->getParam('auth', []);
</div>
<div data-ls-if="0 != {{project-teams.sum}}">
<div class="margin-bottom-small margin-end-small text-align-end text-size-small"><span data-ls-bind="{{project-teams.sum}}"></span> results found</div>
<div class="margin-bottom-small text-align-end text-size-small text-fade"><span data-ls-bind="{{project-teams.sum}}"></span> teams found</div>
<div class="box margin-bottom">
<table class="vertical">
@ -423,13 +423,13 @@ $auth = $this->getParam('auth', []);
data-scope="console">
<ul class="tiles cell-3 margin-bottom-small">
<?php foreach ($providers as $provider => $data):
if (isset($data['enabled']) && !$data['enabled']) {continue;}
if (isset($data['mock']) && $data['mock']) {continue;}
$sandbox = $data['sandbox'] ?? false;
$form = $data['form'] ?? false;
$name = $data['name'] ?? 'Unknown';
$beta = $data['beta'] ?? false;
?>
if (isset($data['enabled']) && !$data['enabled']) {continue;}
// if (isset($data['mock']) && $data['mock']) {continue;}
$sandbox = $data['sandbox'] ?? false;
$form = $data['form'] ?? false;
$name = $data['name'] ?? 'Unknown';
$beta = $data['beta'] ?? false;
?>
<li class="<?php echo (isset($data['enabled']) && !$data['enabled']) ? 'dev-feature' : ''; ?>">
<div data-ui-modal class="modal close" data-button-alias="none" data-open-event="provider-update-<?php echo $provider; ?>">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>

View file

@ -84,7 +84,7 @@
</div>
<div data-ls-if="0 != {{project-members.sum}}">
<div class="margin-bottom-small margin-end-small text-align-end text-size-small margin-top-negative"><span data-ls-bind="{{project-members.sum}}"></span> memberships found</div>
<div class="margin-bottom-small margin-end-small text-align-end text-size-small margin-top-negative text-fade"><span data-ls-bind="{{project-members.sum}}"></span> memberships found</div>
<div class="box margin-bottom">
<ul data-ls-loop="project-members.memberships" data-ls-as="member" class="list">

View file

@ -17,6 +17,108 @@
</h1>
</div>
<div data-ui-modal class="modal width-medium box close" data-button-hide="on" data-open-event="open-update-name">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
<h2>Update name</h2>
<form name="users.updateName"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Update User Name"
data-service="users.updateName"
data-scope="sdk"
data-event="submit"
data-success="trigger,alert"
data-success-param-alert-text="User name was updated successfully"
data-success-param-trigger-events="users.update"
data-failure="alert"
data-failure-param-alert-text="Failed to update user name"
data-failure-param-alert-classname="error">
<input type="hidden" disabled name="userId" data-ls-bind="{{user.$id}}">
<label for="name">Name</label>
<input name="name" id="name" type="text" autocomplete="off" data-ls-bind="{{user.name}}" required class="full-width" maxlength="128">
<hr />
<footer>
<button type="submit" class="">Update</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</footer>
</form>
</div>
<div data-ui-modal class="modal width-medium box close" data-button-hide="on" data-open-event="open-update-email">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
<h2>Update Email</h2>
<form name="users.updateEmail"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Update User Email"
data-service="users.updateEmail"
data-scope="sdk"
data-event="submit"
data-success="trigger,alert"
data-success-param-alert-text="User email was updated successfully"
data-success-param-trigger-events="users.update"
data-failure="alert"
data-failure-param-alert-text="Failed to update user email"
data-failure-param-alert-classname="error">
<input type="hidden" disabled name="userId" data-ls-bind="{{user.$id}}">
<label for="email">Email</label>
<input name="email" id="email" type="text" autocomplete="off" data-ls-bind="{{user.email}}" required class="full-width" maxlength="128">
<hr />
<footer>
<button type="submit" class="">Update</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</footer>
</form>
</div>
<div data-ui-modal class="modal width-medium box close" data-button-hide="on" data-open-event="open-update-password">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
<h2>Update Password</h2>
<form name="users.updatePassword"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Update User Password"
data-service="users.updatePassword"
data-scope="sdk"
data-event="submit"
data-success="trigger,alert"
data-success-param-alert-text="User password was updated successfully"
data-success-param-trigger-events="users.update"
data-failure="alert"
data-failure-param-alert-text="Failed to update user password"
data-failure-param-alert-classname="error">
<input type="hidden" disabled name="userId" data-ls-bind="{{user.$id}}">
<label for="password">Password</label>
<input name="password" id="password" type="password" autocomplete="off" required class="full-width" maxlength="128">
<hr />
<footer>
<button type="submit" class="">Update</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</footer>
</form>
</div>
<div data-ui-modal class="modal width-large box close" data-button-hide="on" data-open-event="open-json">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
@ -134,6 +236,9 @@
</div>
<ul class="margin-bottom-large text-fade text-size-small">
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> <button data-ls-ui-trigger="open-update-name" class="link text-size-small">Update name</button></li>
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> <button data-ls-ui-trigger="open-update-email" class="link text-size-small">Update Email</button></li>
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> <button data-ls-ui-trigger="open-update-password" class="link text-size-small">Update Password</button></li>
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> <button data-ls-ui-trigger="open-json" class="link text-size-small">View as JSON</button></li>
</ul>
@ -218,7 +323,7 @@
<button class="danger">Logout</button>
</form>
<div class="pull-start margin-end avatar-container">
<img onerror="this.onerror=null;this.src='/images/unknown.svg'" data-ls-attrs="src={{env.API}}/avatars/browsers/{{session.clientCode|lowercase}}?width=120&height=120&project={{env.PROJECT}},title={{session.clientName}},alt={{session.clientName}}" class="avatar" loading="lazy" width="60" height="60" />
<img onerror="this.onerror=null;this.className='avatar hide'" data-ls-attrs="src={{env.API}}/avatars/browsers/{{session.clientCode|lowercase}}?width=120&height=120&project={{env.PROJECT}},title={{session.clientName}},alt={{session.clientName}}" class="avatar" loading="lazy" width="60" height="60" />
<div data-ls-if="{{session.provider}} !== 'email'" class="corner">
<img data-ls-attrs="src=/images/users/{{session.provider}}.png?buster=<?php echo APP_CACHE_BUSTER; ?>,title={{session.provider}},alt={{session.provider}}" class="avatar xs" loading="lazy" width="30" height="30" />
@ -228,7 +333,7 @@
<span data-ls-bind="{{session.clientName}}"></span> <span data-ls-bind="{{session.clientVersion}}"></span> on <span data-ls-bind="{{session.deviceModel}}"></span> <span data-ls-bind="{{session.osName}}"></span> <span data-ls-bind="{{session.osVersion}}"></span>
<div class="margin-top-small">
<img onerror="this.onerror=null;this.src='/images/unknown.svg'" data-ls-if="{{session.countryCode}} !== '--'" data-ls-attrs="src={{env.API}}/avatars/flags/{{session.countryCode}}?width=120&height=120&project={{env.PROJECT}}" class="avatar xxs margin-end-small inline" />
<img onerror="this.onerror=null;this.className='avatar hide'" data-ls-if="{{session.countryCode}} !== '--'" data-ls-attrs="src={{env.API}}/avatars/flags/{{session.countryCode}}?width=120&height=120&project={{env.PROJECT}}" class="avatar xxs margin-end-small inline" />
<small data-ls-bind="{{session.ip}}"></small> / <small data-ls-bind="{{session.countryName}}"></small>
</div>
</li>
@ -285,12 +390,12 @@
<td data-title="Date: "><span data-ls-bind="{{log.time|dateTime}}"></span></td>
<td data-title="Event: "><span data-ls-bind="{{log.event}}"></span></td>
<td data-title="Client: ">
<img onerror="this.onerror=null;this.src='/images/unknown.svg'" data-ls-if="({{log.clientCode}})" data-ls-attrs="src={{env.API}}/avatars/browsers/{{log.clientCode|lowercase}}?width=80&height=80&project={{env.PROJECT}},title={{log.clientName}},alt={{log.clientName}}" class="avatar xxs inline margin-end-small" />
<img onerror="this.onerror=null;this.className='avatar hide'" data-ls-if="({{log.clientCode}})" data-ls-attrs="src={{env.API}}/avatars/browsers/{{log.clientCode|lowercase}}?width=80&height=80&project={{env.PROJECT}},title={{log.clientName}},alt={{log.clientName}}" class="avatar xxs inline margin-end-small" />
<span data-ls-if="({{log.clientName}})" data-ls-bind="{{log.clientName}} {{log.clientVersion}} on {{log.model}} {{log.osName}} {{log.osVersion}}"></span>
<div data-ls-if="(!{{log.clientName}})" class="text-align-center text-fade">Unknown</div>
</td>
<td data-title="Location: ">
<img onerror="this.onerror=null;this.src='/images/unknown.svg'" data-ls-if="{{log.countryCode}} !== '--'" data-ls-attrs="src={{env.API}}/avatars/flags/{{log.countryCode}}?width=80&height=80&project={{env.PROJECT}}" class="avatar xxs inline margin-end-small" />
<img onerror="this.onerror=null;this.className='avatar hide'" data-ls-if="{{log.countryCode}} !== '--'" data-ls-attrs="src={{env.API}}/avatars/flags/{{log.countryCode}}?width=80&height=80&project={{env.PROJECT}}" class="avatar xxs inline margin-end-small" />
<span data-ls-bind="{{log.countryName}}"></span>
</td>
<td data-title="IP: "><span data-ls-bind="{{log.ip}}"></span></td>

View file

@ -9,7 +9,7 @@ $image = $this->getParam('image', '');
services:
traefik:
image: traefik:2.3
image: traefik:2.5
container_name: appwrite-traefik
command:
- --providers.file.directory=/storage/config
@ -294,6 +294,26 @@ services:
- _APP_MAINTENANCE_RETENTION_ABUSE
- _APP_MAINTENANCE_RETENTION_AUDIT
appwrite-usage:
image: <?php echo $organization; ?>/<?php echo $image; ?>:<?php echo $version."\n"; ?>
entrypoint: usage
container_name: appwrite-usage
restart: unless-stopped
networks:
- appwrite
depends_on:
- influxdb
- mariadb
environment:
- _APP_ENV
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_INFLUXDB_HOST
- _APP_INFLUXDB_PORT
- _APP_USAGE_AGGREGATION_INTERVAL
appwrite-schedule:
image: <?php echo $organization; ?>/<?php echo $image; ?>:<?php echo $version."\n"; ?>
@ -324,7 +344,7 @@ services:
- MYSQL_DATABASE=${_APP_DB_SCHEMA}
- MYSQL_USER=${_APP_DB_USER}
- MYSQL_PASSWORD=${_APP_DB_PASS}
command: 'mysqld --innodb-flush-method=fsync --wait_timeout=86400'
command: 'mysqld --innodb-flush-method=fsync'
redis:
image: redis:6.0-alpine3.12

View file

@ -1,34 +0,0 @@
<?php
use Appwrite\Extend\PDO;
use Utopia\App;
/** @var Utopia\Registry\Registry $register */
require_once __DIR__.'/init.php';
$register->set('db', function () {
$dbHost = App::getEnv('_APP_DB_HOST', '');
$dbUser = App::getEnv('_APP_DB_USER', '');
$dbPass = App::getEnv('_APP_DB_PASS', '');
$dbScheme = App::getEnv('_APP_DB_SCHEMA', '');
$pdo = new PDO("mysql:host={$dbHost};dbname={$dbScheme};charset=utf8mb4", $dbUser, $dbPass, array(
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8mb4',
PDO::ATTR_TIMEOUT => 3, // Seconds
PDO::ATTR_PERSISTENT => true,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
));
return $pdo;
});
$register->set('cache', function () { // Register cache connection
$redis = new Redis();
$redis->pconnect(App::getEnv('_APP_REDIS_HOST', ''), App::getEnv('_APP_REDIS_PORT', ''));
$redis->setOption(Redis::OPT_READ_TIMEOUT, -1);
return $redis;
});

View file

@ -4,7 +4,7 @@ use Appwrite\Resque\Worker;
use Utopia\Audit\Audit;
use Utopia\CLI\Console;
require_once __DIR__.'/../workers.php';
require_once __DIR__.'/../init.php';
Console::title('Audits V1 Worker');
Console::success(APP_NAME.' audits worker v1 has started');

View file

@ -9,7 +9,7 @@ use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\Domains\Domain;
require_once __DIR__.'/../workers.php';
require_once __DIR__.'/../init.php';
Console::title('Certificates V1 Worker');
Console::success(APP_NAME.' certificates worker v1 has started');

View file

@ -5,7 +5,7 @@ use Utopia\CLI\Console;
use Utopia\Database\Document;
use Utopia\Database\Validator\Authorization;
require_once __DIR__.'/../workers.php';
require_once __DIR__.'/../init.php';
Console::title('Database V1 Worker');
Console::success(APP_NAME.' database worker v1 has started'."\n");
@ -20,31 +20,34 @@ class DatabaseV1 extends Worker
public function run(): void
{
Authorization::disable();
$projectId = $this->args['projectId'] ?? '';
$type = $this->args['type'] ?? '';
$collection = $this->args['collection'] ?? [];
$collection = new Document($collection);
$document = $this->args['document'] ?? [];
$document = new Document($document);
Authorization::disable();
if($collection->isEmpty()) {
throw new Exception('Missing collection');
}
if($document->isEmpty()) {
throw new Exception('Missing document');
}
switch (strval($type)) {
case DATABASE_TYPE_CREATE_ATTRIBUTE:
$attribute = $this->args['document'] ?? '';
$attribute = new Document($attribute);
$this->createAttribute($attribute, $projectId);
$this->createAttribute($collection, $document, $projectId);
break;
case DATABASE_TYPE_DELETE_ATTRIBUTE:
$attribute = $this->args['document'] ?? '';
$attribute = new Document($attribute);
$this->deleteAttribute($attribute, $projectId);
$this->deleteAttribute($collection, $document, $projectId);
break;
case DATABASE_TYPE_CREATE_INDEX:
$index = $this->args['document'] ?? '';
$index = new Document($index);
$this->createIndex($index, $projectId);
$this->createIndex($collection, $document, $projectId);
break;
case DATABASE_TYPE_DELETE_INDEX:
$index = $this->args['document'] ?? '';
$index = new Document($index);
$this->deleteIndex($index, $projectId);
$this->deleteIndex($collection, $document, $projectId);
break;
default:
@ -60,76 +63,120 @@ class DatabaseV1 extends Worker
}
/**
* @param Document $collection
* @param Document $attribute
* @param string $projectId
*/
protected function createAttribute($attribute, $projectId): void
protected function createAttribute(Document $collection, Document $attribute, string $projectId): void
{
$dbForInternal = $this->getInternalDB($projectId);
$dbForExternal = $this->getExternalDB($projectId);
$collectionId = $attribute->getCollection();
$id = $attribute->getAttribute('$id', '');
$collectionId = $collection->getId();
$key = $attribute->getAttribute('key', '');
$type = $attribute->getAttribute('type', '');
$size = $attribute->getAttribute('size', 0);
$required = $attribute->getAttribute('required', false);
$default = $attribute->getAttribute('default', null);
$signed = $attribute->getAttribute('signed', true);
$array = $attribute->getAttribute('array', false);
$format = $attribute->getAttribute('format', null);
$format = $attribute->getAttribute('format', '');
$formatOptions = $attribute->getAttribute('formatOptions', []);
$filters = $attribute->getAttribute('filters', []);
$success = $dbForExternal->createAttribute($collectionId, $id, $type, $size, $required, $default, $signed, $array, $format, $filters);
if ($success) {
$removed = $dbForExternal->removeAttributeInQueue($collectionId, $id);
try {
if(!$dbForExternal->createAttribute($collectionId, $key, $type, $size, $required, $default, $signed, $array, $format, $formatOptions, $filters)) {
throw new Exception('Failed to create Attribute');
}
$dbForInternal->updateDocument('attributes', $attribute->getId(), $attribute->setAttribute('status', 'available'));
} catch (\Throwable $th) {
Console::error($th->getMessage());
$dbForInternal->updateDocument('attributes', $attribute->getId(), $attribute->setAttribute('status', 'failed'));
}
$dbForInternal->deleteCachedDocument('collections', $collectionId);
}
/**
* @param Document $collection
* @param Document $attribute
* @param string $projectId
*/
protected function deleteAttribute($attribute, $projectId): void
protected function deleteAttribute(Document $collection, Document $attribute, string $projectId): void
{
$dbForInternal = $this->getInternalDB($projectId);
$dbForExternal = $this->getExternalDB($projectId);
$collectionId = $collection->getId();
$key = $attribute->getAttribute('key', '');
$collectionId = $attribute->getCollection();
$id = $attribute->getAttribute('$id');
try {
if(!$dbForExternal->deleteAttribute($collectionId, $key)) {
throw new Exception('Failed to delete Attribute');
}
$success = $dbForExternal->deleteAttribute($collectionId, $id);
$dbForInternal->deleteDocument('attributes', $attribute->getId());
} catch (\Throwable $th) {
Console::error($th->getMessage());
$dbForInternal->updateDocument('attributes', $attribute->getId(), $attribute->setAttribute('status', 'failed'));
}
$dbForInternal->deleteCachedDocument('collections', $collectionId);
}
/**
* @param Document $collection
* @param Document $index
* @param string $projectId
*/
protected function createIndex($index, $projectId): void
protected function createIndex(Document $collection, Document $index, string $projectId): void
{
$dbForInternal = $this->getInternalDB($projectId);
$dbForExternal = $this->getExternalDB($projectId);
$collectionId = $index->getCollection();
$id = $index->getAttribute('$id', '');
$collectionId = $collection->getId();
$key = $index->getAttribute('key', '');
$type = $index->getAttribute('type', '');
$attributes = $index->getAttribute('attributes', []);
$lengths = $index->getAttribute('lengths', []);
$orders = $index->getAttribute('orders', []);
$success = $dbForExternal->createIndex($collectionId, $id, $type, $attributes, $lengths, $orders);
if ($success) {
$dbForExternal->removeIndexInQueue($collectionId, $id);
try {
if(!$dbForExternal->createIndex($collectionId, $key, $type, $attributes, $lengths, $orders)) {
throw new Exception('Failed to create Index');
}
$dbForInternal->updateDocument('indexes', $index->getId(), $index->setAttribute('status', 'available'));
} catch (\Throwable $th) {
Console::error($th->getMessage());
$dbForInternal->updateDocument('indexes', $index->getId(), $index->setAttribute('status', 'failed'));
}
$dbForInternal->deleteCachedDocument('collections', $collectionId);
}
/**
* @param Document $collection
* @param Document $index
* @param string $projectId
*/
protected function deleteIndex($index, $projectId): void
protected function deleteIndex(Document $collection, Document $index, string $projectId): void
{
$dbForInternal = $this->getInternalDB($projectId);
$dbForExternal = $this->getExternalDB($projectId);
$collectionId = $index->getCollection();
$id = $index->getAttribute('$id');
$collectionId = $collection->getId();
$key = $index->getAttribute('key');
$success = $dbForExternal->deleteIndex($collectionId, $id);
try {
if(!$dbForExternal->deleteIndex($collectionId, $key)) {
throw new Exception('Failed to delete Attribute');
}
$dbForInternal->deleteDocument('indexes', $index->getId());
} catch (\Throwable $th) {
Console::error($th->getMessage());
$dbForInternal->updateDocument('indexes', $index->getId(), $index->setAttribute('status', 'failed'));
}
$dbForInternal->deleteCachedDocument('collections', $collectionId);
}
}

View file

@ -11,15 +11,21 @@ use Utopia\Abuse\Adapters\TimeLimit;
use Utopia\CLI\Console;
use Utopia\Audit\Audit;
require_once __DIR__.'/../workers.php';
require_once __DIR__.'/../init.php';
Console::title('Deletes V1 Worker');
Console::success(APP_NAME.' deletes worker v1 has started'."\n");
class DeletesV1 extends Worker
{
/**
* @var array
*/
public $args = [];
/**
* @var Database
*/
protected $consoleDB = null;
public function init(): void
@ -33,11 +39,14 @@ class DeletesV1 extends Worker
switch (strval($type)) {
case DELETE_TYPE_DOCUMENT:
$document = $this->args['document'] ?? '';
$document = $this->args['document'] ?? [];
$document = new Document($document);
switch ($document->getCollection()) {
// TODO@kodumbeats define these as constants somewhere
case 'collections':
$this->deleteCollection($document, $projectId);
break;
case 'projects':
$this->deleteProject($document);
break;
@ -72,7 +81,10 @@ class DeletesV1 extends Worker
$document = new Document($this->args['document']);
$this->deleteCertificates($document);
break;
case DELETE_TYPE_USAGE:
$this->deleteUsageStats($this->args['timestamp1d'], $this->args['timestamp30m']);
break;
default:
Console::error('No delete operation for type: '.$type);
break;
@ -82,12 +94,58 @@ class DeletesV1 extends Worker
public function shutdown(): void
{
}
/**
* @param Document $document teams document
* @param string $projectId
*/
protected function deleteCollection(Document $document, string $projectId): void
{
$collectionId = $document->getId();
$dbForInternal = $this->getInternalDB($projectId);
$dbForExternal = $this->getExternalDB($projectId);
$this->deleteByGroup('attributes', [
new Query('collectionId', Query::TYPE_EQUAL, [$collectionId])
], $dbForInternal);
$this->deleteByGroup('indexes', [
new Query('collectionId', Query::TYPE_EQUAL, [$collectionId])
], $dbForInternal);
$dbForExternal->deleteCollection($collectionId);
}
/**
* @param int $timestamp1d
* @param int $timestamp30m
*/
protected function deleteUsageStats(int $timestamp1d, int $timestamp30m) {
$this->deleteForProjectIds(function($projectId) use ($timestamp1d, $timestamp30m) {
if (!($dbForInternal = $this->getInternalDB($projectId))) {
throw new Exception('Failed to get projectDB for project '.$projectId);
}
// Delete Usage stats
$this->deleteByGroup('stats', [
new Query('time', Query::TYPE_LESSER, [$timestamp1d]),
new Query('period', Query::TYPE_EQUAL, ['1d']),
], $dbForInternal);
$this->deleteByGroup('stats', [
new Query('time', Query::TYPE_LESSER, [$timestamp30m]),
new Query('period', Query::TYPE_EQUAL, ['30m']),
], $dbForInternal);
});
}
/**
* @param Document $document teams document
* @param string $projectId
*/
protected function deleteMemberships(Document $document, $projectId) {
protected function deleteMemberships(Document $document, string $projectId): void
{
$teamId = $document->getAttribute('teamId', '');
// Delete Memberships
@ -99,7 +157,7 @@ class DeletesV1 extends Worker
/**
* @param Document $document project document
*/
protected function deleteProject(Document $document)
protected function deleteProject(Document $document): void
{
$projectId = $document->getId();
// Delete all DBs
@ -118,7 +176,7 @@ class DeletesV1 extends Worker
* @param Document $document user document
* @param string $projectId
*/
protected function deleteUser(Document $document, $projectId)
protected function deleteUser(Document $document, string $projectId): void
{
$userId = $document->getId();
@ -143,9 +201,9 @@ class DeletesV1 extends Worker
/**
* @param int $timestamp
*/
protected function deleteExecutionLogs($timestamp)
protected function deleteExecutionLogs(int $timestamp): void
{
$this->deleteForProjectIds(function($projectId) use ($timestamp) {
$this->deleteForProjectIds(function(string $projectId) use ($timestamp) {
if (!($dbForInternal = $this->getInternalDB($projectId))) {
throw new Exception('Failed to get projectDB for project '.$projectId);
}
@ -160,7 +218,7 @@ class DeletesV1 extends Worker
/**
* @param int $timestamp
*/
protected function deleteAbuseLogs($timestamp)
protected function deleteAbuseLogs(int $timestamp): void
{
if($timestamp == 0) {
throw new Exception('Failed to delete audit logs. No timestamp provided');
@ -180,7 +238,7 @@ class DeletesV1 extends Worker
/**
* @param int $timestamp
*/
protected function deleteAuditLogs($timestamp)
protected function deleteAuditLogs(int $timestamp): void
{
if($timestamp == 0) {
throw new Exception('Failed to delete audit logs. No timestamp provided');
@ -198,7 +256,7 @@ class DeletesV1 extends Worker
* @param Document $document function document
* @param string $projectId
*/
protected function deleteFunction(Document $document, $projectId)
protected function deleteFunction(Document $document, string $projectId): void
{
$dbForInternal = $this->getInternalDB($projectId);
$device = new Local(APP_STORAGE_FUNCTIONS.'/app-'.$projectId);
@ -255,7 +313,7 @@ class DeletesV1 extends Worker
/**
* @param callable $callback
*/
protected function deleteForProjectIds(callable $callback)
protected function deleteForProjectIds(callable $callback): void
{
$count = 0;
$chunk = 0;
@ -266,12 +324,12 @@ class DeletesV1 extends Worker
$executionStart = \microtime(true);
while($sum === $limit) {
$chunk++;
Authorization::disable();
$projects = $this->getConsoleDB()->find('projects', [], $limit);
$projects = $this->getConsoleDB()->find('projects', [], $limit, ($chunk * $limit));
Authorization::reset();
$chunk++;
$projectIds = array_map (function ($project) {
return $project->getId();
}, $projects);
@ -295,7 +353,7 @@ class DeletesV1 extends Worker
* @param Database $database
* @param callable $callback
*/
protected function deleteByGroup(string $collection, array $queries, Database $database, callable $callback = null)
protected function deleteByGroup(string $collection, array $queries, Database $database, callable $callback = null): void
{
$count = 0;
$chunk = 0;
@ -331,9 +389,8 @@ class DeletesV1 extends Worker
/**
* @param Document $document certificates document
* @return Database
*/
protected function deleteCertificates(Document $document)
protected function deleteCertificates(Document $document): void
{
$domain = $document->getAttribute('domain');
$directory = APP_STORAGE_CERTIFICATES . '/' . $domain;

View file

@ -13,7 +13,7 @@ use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Validator\Authorization;
require_once __DIR__.'/../workers.php';
require_once __DIR__.'/../init.php';
Runtime::enableCoroutine(0);

View file

@ -6,7 +6,7 @@ use Utopia\App;
use Utopia\CLI\Console;
use Utopia\Locale\Locale;
require_once __DIR__ . '/../workers.php';
require_once __DIR__ . '/../init.php';
Console::title('Mails V1 Worker');
Console::success(APP_NAME . ' mails worker v1 has started' . "\n");

View file

@ -4,7 +4,7 @@ use Appwrite\Resque\Worker;
use Utopia\App;
use Utopia\CLI\Console;
require_once __DIR__.'/../workers.php';
require_once __DIR__.'/../init.php';
Console::title('Webhooks V1 Worker');
Console::success(APP_NAME.' webhooks worker v1 has started');

3
bin/usage Executable file
View file

@ -0,0 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php usage $@

View file

@ -38,14 +38,14 @@
"appwrite/php-clamav": "1.1.*",
"appwrite/php-runtimes": "0.4.*",
"utopia-php/framework": "0.17.*",
"utopia-php/framework": "0.18.*",
"utopia-php/abuse": "0.6.*",
"utopia-php/analytics": "0.2.*",
"utopia-php/audit": "0.6.*",
"utopia-php/cache": "0.4.*",
"utopia-php/cli": "0.11.*",
"utopia-php/config": "0.2.*",
"utopia-php/database": "0.7.*",
"utopia-php/database": "0.10.0",
"utopia-php/locale": "0.4.*",
"utopia-php/registry": "0.5.*",
"utopia-php/preloader": "0.2.*",
@ -53,7 +53,7 @@
"utopia-php/swoole": "0.2.*",
"utopia-php/storage": "0.5.*",
"utopia-php/image": "0.5.*",
"resque/php-resque": "1.3.6",
"matomo/device-detector": "4.2.3",
"dragonmantank/cron-expression": "3.1.0",

187
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": "7de5dc8a9fe3cbc14696c685d1cdddee",
"content-hash": "dfb8fa19daa736b3687617c98f309983",
"packages": [
{
"name": "adhocore/jwt",
@ -248,16 +248,16 @@
},
{
"name": "chillerlan/php-settings-container",
"version": "2.1.1",
"version": "2.1.2",
"source": {
"type": "git",
"url": "https://github.com/chillerlan/php-settings-container.git",
"reference": "98ccc1b31b31a53bcb563465c4961879b2b93096"
"reference": "ec834493a88682dd69652a1eeaf462789ed0c5f5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/chillerlan/php-settings-container/zipball/98ccc1b31b31a53bcb563465c4961879b2b93096",
"reference": "98ccc1b31b31a53bcb563465c4961879b2b93096",
"url": "https://api.github.com/repos/chillerlan/php-settings-container/zipball/ec834493a88682dd69652a1eeaf462789ed0c5f5",
"reference": "ec834493a88682dd69652a1eeaf462789ed0c5f5",
"shasum": ""
},
"require": {
@ -307,7 +307,7 @@
"type": "ko_fi"
}
],
"time": "2021-01-06T15:57:03+00:00"
"time": "2021-09-06T15:17:01+00:00"
},
{
"name": "colinmollenhour/credis",
@ -355,16 +355,16 @@
},
{
"name": "composer/package-versions-deprecated",
"version": "1.11.99.2",
"version": "1.11.99.4",
"source": {
"type": "git",
"url": "https://github.com/composer/package-versions-deprecated.git",
"reference": "c6522afe5540d5fc46675043d3ed5a45a740b27c"
"reference": "b174585d1fe49ceed21928a945138948cb394600"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/c6522afe5540d5fc46675043d3ed5a45a740b27c",
"reference": "c6522afe5540d5fc46675043d3ed5a45a740b27c",
"url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/b174585d1fe49ceed21928a945138948cb394600",
"reference": "b174585d1fe49ceed21928a945138948cb394600",
"shasum": ""
},
"require": {
@ -408,7 +408,7 @@
"description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)",
"support": {
"issues": "https://github.com/composer/package-versions-deprecated/issues",
"source": "https://github.com/composer/package-versions-deprecated/tree/1.11.99.2"
"source": "https://github.com/composer/package-versions-deprecated/tree/1.11.99.4"
},
"funding": [
{
@ -424,7 +424,7 @@
"type": "tidelift"
}
],
"time": "2021-05-24T07:46:03+00:00"
"time": "2021-09-13T08:41:34+00:00"
},
{
"name": "dragonmantank/cron-expression",
@ -1666,22 +1666,22 @@
},
{
"name": "utopia-php/abuse",
"version": "0.6.2",
"version": "0.6.3",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/abuse.git",
"reference": "4cd9c16610f7398d2e1737663ef682fa721ae736"
"reference": "d63e928c2c50b367495a499a85ba9806ee274c5e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/abuse/zipball/4cd9c16610f7398d2e1737663ef682fa721ae736",
"reference": "4cd9c16610f7398d2e1737663ef682fa721ae736",
"url": "https://api.github.com/repos/utopia-php/abuse/zipball/d63e928c2c50b367495a499a85ba9806ee274c5e",
"reference": "d63e928c2c50b367495a499a85ba9806ee274c5e",
"shasum": ""
},
"require": {
"ext-pdo": "*",
"php": ">=7.4",
"utopia-php/database": "0.7.*"
"utopia-php/database": ">=0.6 <1.0"
},
"require-dev": {
"phpunit/phpunit": "^9.4",
@ -1713,9 +1713,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/abuse/issues",
"source": "https://github.com/utopia-php/abuse/tree/0.6.2"
"source": "https://github.com/utopia-php/abuse/tree/0.6.3"
},
"time": "2021-08-13T07:52:34+00:00"
"time": "2021-08-16T18:38:31+00:00"
},
{
"name": "utopia-php/analytics",
@ -1774,22 +1774,22 @@
},
{
"name": "utopia-php/audit",
"version": "0.6.2",
"version": "0.6.3",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/audit.git",
"reference": "2ec39a53eb98a5f9d230550ad56c7c04de5d77df"
"reference": "d79b467fbc7d03e5e02f12cdeb08761507a60ca0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/audit/zipball/2ec39a53eb98a5f9d230550ad56c7c04de5d77df",
"reference": "2ec39a53eb98a5f9d230550ad56c7c04de5d77df",
"url": "https://api.github.com/repos/utopia-php/audit/zipball/d79b467fbc7d03e5e02f12cdeb08761507a60ca0",
"reference": "d79b467fbc7d03e5e02f12cdeb08761507a60ca0",
"shasum": ""
},
"require": {
"ext-pdo": "*",
"php": ">=7.4",
"utopia-php/database": "0.7.*"
"utopia-php/database": ">=0.6 <1.0"
},
"require-dev": {
"phpunit/phpunit": "^9.3",
@ -1821,9 +1821,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/audit/issues",
"source": "https://github.com/utopia-php/audit/tree/0.6.2"
"source": "https://github.com/utopia-php/audit/tree/0.6.3"
},
"time": "2021-08-13T08:05:20+00:00"
"time": "2021-08-16T18:49:55+00:00"
},
{
"name": "utopia-php/cache",
@ -1984,16 +1984,16 @@
},
{
"name": "utopia-php/database",
"version": "0.7.0",
"version": "0.10.0",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/database.git",
"reference": "46c4a99347397e362a9429826e1888b0aefb2056"
"reference": "b7c60b0ec769a9050dd2b939b78ff1f5d4fa27e8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/database/zipball/46c4a99347397e362a9429826e1888b0aefb2056",
"reference": "46c4a99347397e362a9429826e1888b0aefb2056",
"url": "https://api.github.com/repos/utopia-php/database/zipball/b7c60b0ec769a9050dd2b939b78ff1f5d4fa27e8",
"reference": "b7c60b0ec769a9050dd2b939b78ff1f5d4fa27e8",
"shasum": ""
},
"require": {
@ -2041,9 +2041,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/database/issues",
"source": "https://github.com/utopia-php/database/tree/0.7.0"
"source": "https://github.com/utopia-php/database/tree/0.10.0"
},
"time": "2021-08-10T19:09:58+00:00"
"time": "2021-10-04T17:23:25+00:00"
},
{
"name": "utopia-php/domains",
@ -2101,16 +2101,16 @@
},
{
"name": "utopia-php/framework",
"version": "0.17.3",
"version": "0.18.0",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/framework.git",
"reference": "0274f6b3e49db2af0d702edf278ec7504dc99878"
"reference": "f577522a5eb8009967b893fb7ad4ee70d3f7c0db"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/framework/zipball/0274f6b3e49db2af0d702edf278ec7504dc99878",
"reference": "0274f6b3e49db2af0d702edf278ec7504dc99878",
"url": "https://api.github.com/repos/utopia-php/framework/zipball/f577522a5eb8009967b893fb7ad4ee70d3f7c0db",
"reference": "f577522a5eb8009967b893fb7ad4ee70d3f7c0db",
"shasum": ""
},
"require": {
@ -2144,9 +2144,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/framework/issues",
"source": "https://github.com/utopia-php/framework/tree/0.17.3"
"source": "https://github.com/utopia-php/framework/tree/0.18.0"
},
"time": "2021-08-03T13:57:01+00:00"
"time": "2021-08-19T04:58:47+00:00"
},
{
"name": "utopia-php/image",
@ -2582,16 +2582,16 @@
"packages-dev": [
{
"name": "amphp/amp",
"version": "v2.6.0",
"version": "v2.6.1",
"source": {
"type": "git",
"url": "https://github.com/amphp/amp.git",
"reference": "caa95edeb1ca1bf7532e9118ede4a3c3126408cc"
"reference": "c5fc66a78ee38d7ac9195a37bacaf940eb3f65ae"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/amphp/amp/zipball/caa95edeb1ca1bf7532e9118ede4a3c3126408cc",
"reference": "caa95edeb1ca1bf7532e9118ede4a3c3126408cc",
"url": "https://api.github.com/repos/amphp/amp/zipball/c5fc66a78ee38d7ac9195a37bacaf940eb3f65ae",
"reference": "c5fc66a78ee38d7ac9195a37bacaf940eb3f65ae",
"shasum": ""
},
"require": {
@ -2659,7 +2659,7 @@
"support": {
"irc": "irc://irc.freenode.org/amphp",
"issues": "https://github.com/amphp/amp/issues",
"source": "https://github.com/amphp/amp/tree/v2.6.0"
"source": "https://github.com/amphp/amp/tree/v2.6.1"
},
"funding": [
{
@ -2667,7 +2667,7 @@
"type": "github"
}
],
"time": "2021-07-16T20:06:06+00:00"
"time": "2021-09-23T18:43:08+00:00"
},
{
"name": "amphp/byte-stream",
@ -3389,16 +3389,16 @@
},
{
"name": "nikic/php-parser",
"version": "v4.12.0",
"version": "v4.13.0",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
"reference": "6608f01670c3cc5079e18c1dab1104e002579143"
"reference": "50953a2691a922aa1769461637869a0a2faa3f53"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/6608f01670c3cc5079e18c1dab1104e002579143",
"reference": "6608f01670c3cc5079e18c1dab1104e002579143",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/50953a2691a922aa1769461637869a0a2faa3f53",
"reference": "50953a2691a922aa1769461637869a0a2faa3f53",
"shasum": ""
},
"require": {
@ -3439,9 +3439,9 @@
],
"support": {
"issues": "https://github.com/nikic/PHP-Parser/issues",
"source": "https://github.com/nikic/PHP-Parser/tree/v4.12.0"
"source": "https://github.com/nikic/PHP-Parser/tree/v4.13.0"
},
"time": "2021-07-21T10:44:31+00:00"
"time": "2021-09-20T12:20:58+00:00"
},
{
"name": "openlss/lib-array2xml",
@ -3718,16 +3718,16 @@
},
{
"name": "phpdocumentor/type-resolver",
"version": "1.4.0",
"version": "1.5.1",
"source": {
"type": "git",
"url": "https://github.com/phpDocumentor/TypeResolver.git",
"reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0"
"reference": "a12f7e301eb7258bb68acd89d4aefa05c2906cae"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0",
"reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0",
"url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/a12f7e301eb7258bb68acd89d4aefa05c2906cae",
"reference": "a12f7e301eb7258bb68acd89d4aefa05c2906cae",
"shasum": ""
},
"require": {
@ -3735,7 +3735,8 @@
"phpdocumentor/reflection-common": "^2.0"
},
"require-dev": {
"ext-tokenizer": "*"
"ext-tokenizer": "*",
"psalm/phar": "^4.8"
},
"type": "library",
"extra": {
@ -3761,39 +3762,39 @@
"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.4.0"
"source": "https://github.com/phpDocumentor/TypeResolver/tree/1.5.1"
},
"time": "2020-09-17T18:55:26+00:00"
"time": "2021-10-02T14:08:47+00:00"
},
{
"name": "phpspec/prophecy",
"version": "1.13.0",
"version": "1.14.0",
"source": {
"type": "git",
"url": "https://github.com/phpspec/prophecy.git",
"reference": "be1996ed8adc35c3fd795488a653f4b518be70ea"
"reference": "d86dfc2e2a3cd366cee475e52c6bb3bbc371aa0e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpspec/prophecy/zipball/be1996ed8adc35c3fd795488a653f4b518be70ea",
"reference": "be1996ed8adc35c3fd795488a653f4b518be70ea",
"url": "https://api.github.com/repos/phpspec/prophecy/zipball/d86dfc2e2a3cd366cee475e52c6bb3bbc371aa0e",
"reference": "d86dfc2e2a3cd366cee475e52c6bb3bbc371aa0e",
"shasum": ""
},
"require": {
"doctrine/instantiator": "^1.2",
"php": "^7.2 || ~8.0, <8.1",
"php": "^7.2 || ~8.0, <8.2",
"phpdocumentor/reflection-docblock": "^5.2",
"sebastian/comparator": "^3.0 || ^4.0",
"sebastian/recursion-context": "^3.0 || ^4.0"
},
"require-dev": {
"phpspec/phpspec": "^6.0",
"phpspec/phpspec": "^6.0 || ^7.0",
"phpunit/phpunit": "^8.0 || ^9.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.11.x-dev"
"dev-master": "1.x-dev"
}
},
"autoload": {
@ -3828,29 +3829,29 @@
],
"support": {
"issues": "https://github.com/phpspec/prophecy/issues",
"source": "https://github.com/phpspec/prophecy/tree/1.13.0"
"source": "https://github.com/phpspec/prophecy/tree/1.14.0"
},
"time": "2021-03-17T13:42:18+00:00"
"time": "2021-09-10T09:02:12+00:00"
},
{
"name": "phpunit/php-code-coverage",
"version": "9.2.6",
"version": "9.2.7",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
"reference": "f6293e1b30a2354e8428e004689671b83871edde"
"reference": "d4c798ed8d51506800b441f7a13ecb0f76f12218"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/f6293e1b30a2354e8428e004689671b83871edde",
"reference": "f6293e1b30a2354e8428e004689671b83871edde",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/d4c798ed8d51506800b441f7a13ecb0f76f12218",
"reference": "d4c798ed8d51506800b441f7a13ecb0f76f12218",
"shasum": ""
},
"require": {
"ext-dom": "*",
"ext-libxml": "*",
"ext-xmlwriter": "*",
"nikic/php-parser": "^4.10.2",
"nikic/php-parser": "^4.12.0",
"php": ">=7.3",
"phpunit/php-file-iterator": "^3.0.3",
"phpunit/php-text-template": "^2.0.2",
@ -3899,7 +3900,7 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.6"
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.7"
},
"funding": [
{
@ -3907,7 +3908,7 @@
"type": "github"
}
],
"time": "2021-03-28T07:26:59+00:00"
"time": "2021-09-17T05:39:03+00:00"
},
{
"name": "phpunit/php-file-iterator",
@ -5319,16 +5320,16 @@
},
{
"name": "symfony/console",
"version": "v5.3.6",
"version": "v5.3.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "51b71afd6d2dc8f5063199357b9880cea8d8bfe2"
"reference": "8b1008344647462ae6ec57559da166c2bfa5e16a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/51b71afd6d2dc8f5063199357b9880cea8d8bfe2",
"reference": "51b71afd6d2dc8f5063199357b9880cea8d8bfe2",
"url": "https://api.github.com/repos/symfony/console/zipball/8b1008344647462ae6ec57559da166c2bfa5e16a",
"reference": "8b1008344647462ae6ec57559da166c2bfa5e16a",
"shasum": ""
},
"require": {
@ -5398,7 +5399,7 @@
"terminal"
],
"support": {
"source": "https://github.com/symfony/console/tree/v5.3.6"
"source": "https://github.com/symfony/console/tree/v5.3.7"
},
"funding": [
{
@ -5414,7 +5415,7 @@
"type": "tidelift"
}
],
"time": "2021-07-27T19:10:22+00:00"
"time": "2021-08-25T20:02:16+00:00"
},
{
"name": "symfony/deprecation-contracts",
@ -5888,16 +5889,16 @@
},
{
"name": "symfony/string",
"version": "v5.3.3",
"version": "v5.3.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
"reference": "bd53358e3eccec6a670b5f33ab680d8dbe1d4ae1"
"reference": "8d224396e28d30f81969f083a58763b8b9ceb0a5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/string/zipball/bd53358e3eccec6a670b5f33ab680d8dbe1d4ae1",
"reference": "bd53358e3eccec6a670b5f33ab680d8dbe1d4ae1",
"url": "https://api.github.com/repos/symfony/string/zipball/8d224396e28d30f81969f083a58763b8b9ceb0a5",
"reference": "8d224396e28d30f81969f083a58763b8b9ceb0a5",
"shasum": ""
},
"require": {
@ -5951,7 +5952,7 @@
"utf8"
],
"support": {
"source": "https://github.com/symfony/string/tree/v5.3.3"
"source": "https://github.com/symfony/string/tree/v5.3.7"
},
"funding": [
{
@ -5967,7 +5968,7 @@
"type": "tidelift"
}
],
"time": "2021-06-27T11:44:38+00:00"
"time": "2021-08-26T08:00:08+00:00"
},
{
"name": "theseer/tokenizer",
@ -6021,16 +6022,16 @@
},
{
"name": "twig/twig",
"version": "v2.14.6",
"version": "v2.14.7",
"source": {
"type": "git",
"url": "https://github.com/twigphp/Twig.git",
"reference": "27e5cf2b05e3744accf39d4c68a3235d9966d260"
"reference": "8e202327ee1ed863629de9b18a5ec70ac614d88f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/27e5cf2b05e3744accf39d4c68a3235d9966d260",
"reference": "27e5cf2b05e3744accf39d4c68a3235d9966d260",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/8e202327ee1ed863629de9b18a5ec70ac614d88f",
"reference": "8e202327ee1ed863629de9b18a5ec70ac614d88f",
"shasum": ""
},
"require": {
@ -6040,7 +6041,7 @@
},
"require-dev": {
"psr/container": "^1.0",
"symfony/phpunit-bridge": "^4.4.9|^5.0.9"
"symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0"
},
"type": "library",
"extra": {
@ -6084,7 +6085,7 @@
],
"support": {
"issues": "https://github.com/twigphp/Twig/issues",
"source": "https://github.com/twigphp/Twig/tree/v2.14.6"
"source": "https://github.com/twigphp/Twig/tree/v2.14.7"
},
"funding": [
{
@ -6096,7 +6097,7 @@
"type": "tidelift"
}
],
"time": "2021-05-16T12:12:47+00:00"
"time": "2021-09-17T08:39:54+00:00"
},
{
"name": "vimeo/psalm",
@ -6278,5 +6279,5 @@
"platform-overrides": {
"php": "8.0"
},
"plugin-api-version": "2.0.0"
"plugin-api-version": "2.1.0"
}

View file

@ -7,7 +7,7 @@ version: '3'
services:
traefik:
image: traefik:2.3
image: traefik:2.5
container_name: appwrite-traefik
command:
- --log.level=DEBUG
@ -73,7 +73,6 @@ services:
- mariadb
- redis
# - clamav
- influxdb
entrypoint:
- php
- -e
@ -112,8 +111,6 @@ services:
- _APP_SMTP_USERNAME
- _APP_SMTP_PASSWORD
- _APP_USAGE_STATS
- _APP_INFLUXDB_HOST
- _APP_INFLUXDB_PORT
- _APP_STORAGE_LIMIT
- _APP_FUNCTIONS_TIMEOUT
- _APP_FUNCTIONS_CONTAINERS
@ -214,6 +211,7 @@ services:
volumes:
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
# - ./vendor/utopia-php/database:/usr/src/code/vendor/utopia-php/database
depends_on:
- redis
- mariadb
@ -348,6 +346,37 @@ services:
- _APP_MAINTENANCE_RETENTION_ABUSE
- _APP_MAINTENANCE_RETENTION_AUDIT
appwrite-usage:
entrypoint:
- php
- -e
- /usr/src/code/app/cli.php
- usage
container_name: appwrite-usage
build:
context: .
args:
- DEBUG=false
networks:
- appwrite
volumes:
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
- ./dev:/usr/local/dev
depends_on:
- influxdb
- mariadb
environment:
- _APP_ENV
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_INFLUXDB_HOST
- _APP_INFLUXDB_PORT
- _APP_USAGE_SYNC_INTERVAL
appwrite-schedule:
entrypoint: schedule
container_name: appwrite-schedule
@ -381,7 +410,7 @@ services:
- MYSQL_DATABASE=${_APP_DB_SCHEMA}
- MYSQL_USER=user
- MYSQL_PASSWORD=${_APP_DB_PASS}
command: 'mysqld --innodb-flush-method=fsync --wait_timeout=86400' # add ' --query_cache_size=0' for DB tests
command: 'mysqld --innodb-flush-method=fsync' # add ' --query_cache_size=0' for DB tests
# command: mv /var/lib/mysql/ib_logfile0 /var/lib/mysql/ib_logfile0.bu && mv /var/lib/mysql/ib_logfile1 /var/lib/mysql/ib_logfile1.bu
# smtp:
@ -399,6 +428,8 @@ services:
redis:
image: redis:6.0-alpine
container_name: appwrite-redis
ports:
- "6379:6379"
networks:
- appwrite
volumes:

View file

@ -5,7 +5,7 @@ sdk
.setProject('5df5acd0d48c2') // Your project ID
;
let promise = sdk.account.create('email@example.com', 'password');
let promise = sdk.account.create('', 'email@example.com', 'password');
promise.then(function (response) {
console.log(response); // Success

View file

@ -5,7 +5,7 @@ sdk
.setProject('5df5acd0d48c2') // Your project ID
;
let promise = sdk.database.createDocument('[COLLECTION_ID]', {});
let promise = sdk.database.createDocument('[COLLECTION_ID]', '', {});
promise.then(function (response) {
console.log(response); // Success

View file

@ -5,7 +5,7 @@ sdk
.setProject('5df5acd0d48c2') // Your project ID
;
let promise = sdk.storage.createFile(document.getElementById('uploader').files[0]);
let promise = sdk.storage.createFile('', document.getElementById('uploader').files[0]);
promise.then(function (response) {
console.log(response); // Success

View file

@ -5,7 +5,7 @@ sdk
.setProject('5df5acd0d48c2') // Your project ID
;
let promise = sdk.teams.create('[NAME]');
let promise = sdk.teams.create('', '[NAME]');
promise.then(function (response) {
console.log(response); // Success

View file

@ -5,7 +5,7 @@ sdk
.setProject('5df5acd0d48c2') // Your project ID
;
let promise = sdk.database.createCollection('', '[NAME]', '', '');
let promise = sdk.database.createCollection('', '[NAME]', 'document', '', '');
promise.then(function (response) {
console.log(response); // Success

View file

@ -5,7 +5,7 @@ sdk
.setProject('5df5acd0d48c2') // Your project ID
;
let promise = sdk.database.createUrlAttribute('[COLLECTION_ID]', '', null, false);
let promise = sdk.database.createUrlAttribute('[COLLECTION_ID]', '', false);
promise.then(function (response) {
console.log(response); // Success

View file

@ -5,7 +5,7 @@ sdk
.setProject('5df5acd0d48c2') // Your project ID
;
let promise = sdk.database.listCollectionLogs('[COLLECTION_ID]');
let promise = sdk.database.getCollectionLogs('[COLLECTION_ID]');
promise.then(function (response) {
console.log(response); // Success

View file

@ -0,0 +1,14 @@
let sdk = new Appwrite();
sdk
.setEndpoint('https://[HOSTNAME_OR_IP]/v1') // Your API Endpoint
.setProject('5df5acd0d48c2') // Your project ID
;
let promise = sdk.database.listCollectionLogs('[COLLECTION_ID]');
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.database.updateCollection('[COLLECTION_ID]', '[NAME]');
let promise = sdk.database.updateCollection('[COLLECTION_ID]', '[NAME]', 'document');
promise.then(function (response) {
console.log(response); // Success

View file

@ -88,22 +88,27 @@ if(typeof size!=='undefined'){payload['size']=size;}
if(typeof margin!=='undefined'){payload['margin']=margin;}
if(typeof download!=='undefined'){payload['download']=download;}
const uri=new URL(this.config.endpoint+path);payload['project']=this.config.project;for(const[key,value]of Object.entries(this.flatten(payload))){uri.searchParams.append(key,value);}
return uri;}};this.database={listCollections:(search,limit,offset,orderType)=>__awaiter(this,void 0,void 0,function*(){let path='/database/collections';let payload={};if(typeof search!=='undefined'){payload['search']=search;}
return uri;}};this.database={listCollections:(search,limit,offset,after,orderType)=>__awaiter(this,void 0,void 0,function*(){let path='/database/collections';let payload={};if(typeof search!=='undefined'){payload['search']=search;}
if(typeof limit!=='undefined'){payload['limit']=limit;}
if(typeof offset!=='undefined'){payload['offset']=offset;}
if(typeof after!=='undefined'){payload['after']=after;}
if(typeof orderType!=='undefined'){payload['orderType']=orderType;}
const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),createCollection:(collectionId,name,read,write)=>__awaiter(this,void 0,void 0,function*(){if(typeof collectionId==='undefined'){throw new AppwriteException('Missing required parameter: "collectionId"');}
const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),createCollection:(collectionId,name,permission,read,write)=>__awaiter(this,void 0,void 0,function*(){if(typeof collectionId==='undefined'){throw new AppwriteException('Missing required parameter: "collectionId"');}
if(typeof name==='undefined'){throw new AppwriteException('Missing required parameter: "name"');}
if(typeof permission==='undefined'){throw new AppwriteException('Missing required parameter: "permission"');}
if(typeof read==='undefined'){throw new AppwriteException('Missing required parameter: "read"');}
if(typeof write==='undefined'){throw new AppwriteException('Missing required parameter: "write"');}
let path='/database/collections';let payload={};if(typeof collectionId!=='undefined'){payload['collectionId']=collectionId;}
if(typeof name!=='undefined'){payload['name']=name;}
if(typeof permission!=='undefined'){payload['permission']=permission;}
if(typeof read!=='undefined'){payload['read']=read;}
if(typeof write!=='undefined'){payload['write']=write;}
const uri=new URL(this.config.endpoint+path);return yield this.call('post',uri,{'content-type':'application/json',},payload);}),getCollection:(collectionId)=>__awaiter(this,void 0,void 0,function*(){if(typeof collectionId==='undefined'){throw new AppwriteException('Missing required parameter: "collectionId"');}
let path='/database/collections/{collectionId}'.replace('{collectionId}',collectionId);let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),updateCollection:(collectionId,name,read,write)=>__awaiter(this,void 0,void 0,function*(){if(typeof collectionId==='undefined'){throw new AppwriteException('Missing required parameter: "collectionId"');}
let path='/database/collections/{collectionId}'.replace('{collectionId}',collectionId);let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),updateCollection:(collectionId,name,permission,read,write)=>__awaiter(this,void 0,void 0,function*(){if(typeof collectionId==='undefined'){throw new AppwriteException('Missing required parameter: "collectionId"');}
if(typeof name==='undefined'){throw new AppwriteException('Missing required parameter: "name"');}
if(typeof permission==='undefined'){throw new AppwriteException('Missing required parameter: "permission"');}
let path='/database/collections/{collectionId}'.replace('{collectionId}',collectionId);let payload={};if(typeof name!=='undefined'){payload['name']=name;}
if(typeof permission!=='undefined'){payload['permission']=permission;}
if(typeof read!=='undefined'){payload['read']=read;}
if(typeof write!=='undefined'){payload['write']=write;}
const uri=new URL(this.config.endpoint+path);return yield this.call('put',uri,{'content-type':'application/json',},payload);}),deleteCollection:(collectionId)=>__awaiter(this,void 0,void 0,function*(){if(typeof collectionId==='undefined'){throw new AppwriteException('Missing required parameter: "collectionId"');}
@ -156,12 +161,10 @@ if(typeof size!=='undefined'){payload['size']=size;}
if(typeof required!=='undefined'){payload['required']=required;}
if(typeof xdefault!=='undefined'){payload['xdefault']=xdefault;}
if(typeof array!=='undefined'){payload['array']=array;}
const uri=new URL(this.config.endpoint+path);return yield this.call('post',uri,{'content-type':'application/json',},payload);}),createUrlAttribute:(collectionId,attributeId,size,required,xdefault,array)=>__awaiter(this,void 0,void 0,function*(){if(typeof collectionId==='undefined'){throw new AppwriteException('Missing required parameter: "collectionId"');}
const uri=new URL(this.config.endpoint+path);return yield this.call('post',uri,{'content-type':'application/json',},payload);}),createUrlAttribute:(collectionId,attributeId,required,xdefault,array)=>__awaiter(this,void 0,void 0,function*(){if(typeof collectionId==='undefined'){throw new AppwriteException('Missing required parameter: "collectionId"');}
if(typeof attributeId==='undefined'){throw new AppwriteException('Missing required parameter: "attributeId"');}
if(typeof size==='undefined'){throw new AppwriteException('Missing required parameter: "size"');}
if(typeof required==='undefined'){throw new AppwriteException('Missing required parameter: "required"');}
let path='/database/collections/{collectionId}/attributes/url'.replace('{collectionId}',collectionId);let payload={};if(typeof attributeId!=='undefined'){payload['attributeId']=attributeId;}
if(typeof size!=='undefined'){payload['size']=size;}
if(typeof required!=='undefined'){payload['required']=required;}
if(typeof xdefault!=='undefined'){payload['xdefault']=xdefault;}
if(typeof array!=='undefined'){payload['array']=array;}
@ -169,10 +172,11 @@ const uri=new URL(this.config.endpoint+path);return yield this.call('post',uri,{
if(typeof attributeId==='undefined'){throw new AppwriteException('Missing required parameter: "attributeId"');}
let path='/database/collections/{collectionId}/attributes/{attributeId}'.replace('{collectionId}',collectionId).replace('{attributeId}',attributeId);let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),deleteAttribute:(collectionId,attributeId)=>__awaiter(this,void 0,void 0,function*(){if(typeof collectionId==='undefined'){throw new AppwriteException('Missing required parameter: "collectionId"');}
if(typeof attributeId==='undefined'){throw new AppwriteException('Missing required parameter: "attributeId"');}
let path='/database/collections/{collectionId}/attributes/{attributeId}'.replace('{collectionId}',collectionId).replace('{attributeId}',attributeId);let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('delete',uri,{'content-type':'application/json',},payload);}),listDocuments:(collectionId,queries,limit,offset,orderAttributes,orderTypes)=>__awaiter(this,void 0,void 0,function*(){if(typeof collectionId==='undefined'){throw new AppwriteException('Missing required parameter: "collectionId"');}
let path='/database/collections/{collectionId}/attributes/{attributeId}'.replace('{collectionId}',collectionId).replace('{attributeId}',attributeId);let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('delete',uri,{'content-type':'application/json',},payload);}),listDocuments:(collectionId,queries,limit,offset,after,orderAttributes,orderTypes)=>__awaiter(this,void 0,void 0,function*(){if(typeof collectionId==='undefined'){throw new AppwriteException('Missing required parameter: "collectionId"');}
let path='/database/collections/{collectionId}/documents'.replace('{collectionId}',collectionId);let payload={};if(typeof queries!=='undefined'){payload['queries']=queries;}
if(typeof limit!=='undefined'){payload['limit']=limit;}
if(typeof offset!=='undefined'){payload['offset']=offset;}
if(typeof after!=='undefined'){payload['after']=after;}
if(typeof orderAttributes!=='undefined'){payload['orderAttributes']=orderAttributes;}
if(typeof orderTypes!=='undefined'){payload['orderTypes']=orderTypes;}
const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),createDocument:(collectionId,documentId,data,read,write)=>__awaiter(this,void 0,void 0,function*(){if(typeof collectionId==='undefined'){throw new AppwriteException('Missing required parameter: "collectionId"');}
@ -201,14 +205,15 @@ let path='/database/collections/{collectionId}/indexes'.replace('{collectionId}'
if(typeof type!=='undefined'){payload['type']=type;}
if(typeof attributes!=='undefined'){payload['attributes']=attributes;}
if(typeof orders!=='undefined'){payload['orders']=orders;}
console.log(collectionId,indexId,type,attributes,orders);const uri=new URL(this.config.endpoint+path);return yield this.call('post',uri,{'content-type':'application/json',},payload);}),getIndex:(collectionId,indexId)=>__awaiter(this,void 0,void 0,function*(){if(typeof collectionId==='undefined'){throw new AppwriteException('Missing required parameter: "collectionId"');}
const uri=new URL(this.config.endpoint+path);return yield this.call('post',uri,{'content-type':'application/json',},payload);}),getIndex:(collectionId,indexId)=>__awaiter(this,void 0,void 0,function*(){if(typeof collectionId==='undefined'){throw new AppwriteException('Missing required parameter: "collectionId"');}
if(typeof indexId==='undefined'){throw new AppwriteException('Missing required parameter: "indexId"');}
let path='/database/collections/{collectionId}/indexes/{indexId}'.replace('{collectionId}',collectionId).replace('{indexId}',indexId);let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),deleteIndex:(collectionId,indexId)=>__awaiter(this,void 0,void 0,function*(){if(typeof collectionId==='undefined'){throw new AppwriteException('Missing required parameter: "collectionId"');}
if(typeof indexId==='undefined'){throw new AppwriteException('Missing required parameter: "indexId"');}
let path='/database/collections/{collectionId}/indexes/{indexId}'.replace('{collectionId}',collectionId).replace('{indexId}',indexId);let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('delete',uri,{'content-type':'application/json',},payload);}),listCollectionLogs:(collectionId)=>__awaiter(this,void 0,void 0,function*(){if(typeof collectionId==='undefined'){throw new AppwriteException('Missing required parameter: "collectionId"');}
let path='/database/collections/{collectionId}/logs'.replace('{collectionId}',collectionId);let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);})};this.functions={list:(search,limit,offset,orderType)=>__awaiter(this,void 0,void 0,function*(){let path='/functions';let payload={};if(typeof search!=='undefined'){payload['search']=search;}
let path='/database/collections/{collectionId}/logs'.replace('{collectionId}',collectionId);let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);})};this.functions={list:(search,limit,offset,after,orderType)=>__awaiter(this,void 0,void 0,function*(){let path='/functions';let payload={};if(typeof search!=='undefined'){payload['search']=search;}
if(typeof limit!=='undefined'){payload['limit']=limit;}
if(typeof offset!=='undefined'){payload['offset']=offset;}
if(typeof after!=='undefined'){payload['after']=after;}
if(typeof orderType!=='undefined'){payload['orderType']=orderType;}
const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),create:(functionId,name,execute,runtime,vars,events,schedule,timeout)=>__awaiter(this,void 0,void 0,function*(){if(typeof functionId==='undefined'){throw new AppwriteException('Missing required parameter: "functionId"');}
if(typeof name==='undefined'){throw new AppwriteException('Missing required parameter: "name"');}
@ -233,9 +238,10 @@ if(typeof events!=='undefined'){payload['events']=events;}
if(typeof schedule!=='undefined'){payload['schedule']=schedule;}
if(typeof timeout!=='undefined'){payload['timeout']=timeout;}
const uri=new URL(this.config.endpoint+path);return yield this.call('put',uri,{'content-type':'application/json',},payload);}),delete:(functionId)=>__awaiter(this,void 0,void 0,function*(){if(typeof functionId==='undefined'){throw new AppwriteException('Missing required parameter: "functionId"');}
let path='/functions/{functionId}'.replace('{functionId}',functionId);let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('delete',uri,{'content-type':'application/json',},payload);}),listExecutions:(functionId,limit,offset)=>__awaiter(this,void 0,void 0,function*(){if(typeof functionId==='undefined'){throw new AppwriteException('Missing required parameter: "functionId"');}
let path='/functions/{functionId}'.replace('{functionId}',functionId);let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('delete',uri,{'content-type':'application/json',},payload);}),listExecutions:(functionId,limit,offset,after)=>__awaiter(this,void 0,void 0,function*(){if(typeof functionId==='undefined'){throw new AppwriteException('Missing required parameter: "functionId"');}
let path='/functions/{functionId}/executions'.replace('{functionId}',functionId);let payload={};if(typeof limit!=='undefined'){payload['limit']=limit;}
if(typeof offset!=='undefined'){payload['offset']=offset;}
if(typeof after!=='undefined'){payload['after']=after;}
const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),createExecution:(functionId,data)=>__awaiter(this,void 0,void 0,function*(){if(typeof functionId==='undefined'){throw new AppwriteException('Missing required parameter: "functionId"');}
let path='/functions/{functionId}/executions'.replace('{functionId}',functionId);let payload={};if(typeof data!=='undefined'){payload['data']=data;}
const uri=new URL(this.config.endpoint+path);return yield this.call('post',uri,{'content-type':'application/json',},payload);}),getExecution:(functionId,executionId)=>__awaiter(this,void 0,void 0,function*(){if(typeof functionId==='undefined'){throw new AppwriteException('Missing required parameter: "functionId"');}
@ -243,10 +249,11 @@ if(typeof executionId==='undefined'){throw new AppwriteException('Missing requir
let path='/functions/{functionId}/executions/{executionId}'.replace('{functionId}',functionId).replace('{executionId}',executionId);let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),updateTag:(functionId,tag)=>__awaiter(this,void 0,void 0,function*(){if(typeof functionId==='undefined'){throw new AppwriteException('Missing required parameter: "functionId"');}
if(typeof tag==='undefined'){throw new AppwriteException('Missing required parameter: "tag"');}
let path='/functions/{functionId}/tag'.replace('{functionId}',functionId);let payload={};if(typeof tag!=='undefined'){payload['tag']=tag;}
const uri=new URL(this.config.endpoint+path);return yield this.call('patch',uri,{'content-type':'application/json',},payload);}),listTags:(functionId,search,limit,offset,orderType)=>__awaiter(this,void 0,void 0,function*(){if(typeof functionId==='undefined'){throw new AppwriteException('Missing required parameter: "functionId"');}
const uri=new URL(this.config.endpoint+path);return yield this.call('patch',uri,{'content-type':'application/json',},payload);}),listTags:(functionId,search,limit,offset,after,orderType)=>__awaiter(this,void 0,void 0,function*(){if(typeof functionId==='undefined'){throw new AppwriteException('Missing required parameter: "functionId"');}
let path='/functions/{functionId}/tags'.replace('{functionId}',functionId);let payload={};if(typeof search!=='undefined'){payload['search']=search;}
if(typeof limit!=='undefined'){payload['limit']=limit;}
if(typeof offset!=='undefined'){payload['offset']=offset;}
if(typeof after!=='undefined'){payload['after']=after;}
if(typeof orderType!=='undefined'){payload['orderType']=orderType;}
const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),createTag:(functionId,command,code)=>__awaiter(this,void 0,void 0,function*(){if(typeof functionId==='undefined'){throw new AppwriteException('Missing required parameter: "functionId"');}
if(typeof command==='undefined'){throw new AppwriteException('Missing required parameter: "command"');}
@ -259,9 +266,10 @@ let path='/functions/{functionId}/tags/{tagId}'.replace('{functionId}',functionI
if(typeof tagId==='undefined'){throw new AppwriteException('Missing required parameter: "tagId"');}
let path='/functions/{functionId}/tags/{tagId}'.replace('{functionId}',functionId).replace('{tagId}',tagId);let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('delete',uri,{'content-type':'application/json',},payload);}),getUsage:(functionId,range)=>__awaiter(this,void 0,void 0,function*(){if(typeof functionId==='undefined'){throw new AppwriteException('Missing required parameter: "functionId"');}
let path='/functions/{functionId}/usage'.replace('{functionId}',functionId);let payload={};if(typeof range!=='undefined'){payload['range']=range;}
const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);})};this.health={get:()=>__awaiter(this,void 0,void 0,function*(){let path='/health';let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),getAntiVirus:()=>__awaiter(this,void 0,void 0,function*(){let path='/health/anti-virus';let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),getCache:()=>__awaiter(this,void 0,void 0,function*(){let path='/health/cache';let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),getDB:()=>__awaiter(this,void 0,void 0,function*(){let path='/health/db';let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),getQueueCertificates:()=>__awaiter(this,void 0,void 0,function*(){let path='/health/queue/certificates';let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),getQueueFunctions:()=>__awaiter(this,void 0,void 0,function*(){let path='/health/queue/functions';let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),getQueueLogs:()=>__awaiter(this,void 0,void 0,function*(){let path='/health/queue/logs';let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),getQueueUsage:()=>__awaiter(this,void 0,void 0,function*(){let path='/health/queue/usage';let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),getQueueWebhooks:()=>__awaiter(this,void 0,void 0,function*(){let path='/health/queue/webhooks';let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),getStorageLocal:()=>__awaiter(this,void 0,void 0,function*(){let path='/health/storage/local';let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),getTime:()=>__awaiter(this,void 0,void 0,function*(){let path='/health/time';let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);})};this.locale={get:()=>__awaiter(this,void 0,void 0,function*(){let path='/locale';let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),getContinents:()=>__awaiter(this,void 0,void 0,function*(){let path='/locale/continents';let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),getCountries:()=>__awaiter(this,void 0,void 0,function*(){let path='/locale/countries';let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),getCountriesEU:()=>__awaiter(this,void 0,void 0,function*(){let path='/locale/countries/eu';let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),getCountriesPhones:()=>__awaiter(this,void 0,void 0,function*(){let path='/locale/countries/phones';let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),getCurrencies:()=>__awaiter(this,void 0,void 0,function*(){let path='/locale/currencies';let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),getLanguages:()=>__awaiter(this,void 0,void 0,function*(){let path='/locale/languages';let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);})};this.projects={list:(search,limit,offset,orderType)=>__awaiter(this,void 0,void 0,function*(){let path='/projects';let payload={};if(typeof search!=='undefined'){payload['search']=search;}
const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);})};this.health={get:()=>__awaiter(this,void 0,void 0,function*(){let path='/health';let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),getAntiVirus:()=>__awaiter(this,void 0,void 0,function*(){let path='/health/anti-virus';let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),getCache:()=>__awaiter(this,void 0,void 0,function*(){let path='/health/cache';let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),getDB:()=>__awaiter(this,void 0,void 0,function*(){let path='/health/db';let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),getQueueCertificates:()=>__awaiter(this,void 0,void 0,function*(){let path='/health/queue/certificates';let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),getQueueFunctions:()=>__awaiter(this,void 0,void 0,function*(){let path='/health/queue/functions';let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),getQueueLogs:()=>__awaiter(this,void 0,void 0,function*(){let path='/health/queue/logs';let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),getQueueUsage:()=>__awaiter(this,void 0,void 0,function*(){let path='/health/queue/usage';let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),getQueueWebhooks:()=>__awaiter(this,void 0,void 0,function*(){let path='/health/queue/webhooks';let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),getStorageLocal:()=>__awaiter(this,void 0,void 0,function*(){let path='/health/storage/local';let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),getTime:()=>__awaiter(this,void 0,void 0,function*(){let path='/health/time';let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);})};this.locale={get:()=>__awaiter(this,void 0,void 0,function*(){let path='/locale';let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),getContinents:()=>__awaiter(this,void 0,void 0,function*(){let path='/locale/continents';let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),getCountries:()=>__awaiter(this,void 0,void 0,function*(){let path='/locale/countries';let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),getCountriesEU:()=>__awaiter(this,void 0,void 0,function*(){let path='/locale/countries/eu';let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),getCountriesPhones:()=>__awaiter(this,void 0,void 0,function*(){let path='/locale/countries/phones';let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),getCurrencies:()=>__awaiter(this,void 0,void 0,function*(){let path='/locale/currencies';let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),getLanguages:()=>__awaiter(this,void 0,void 0,function*(){let path='/locale/languages';let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);})};this.projects={list:(search,limit,offset,after,orderType)=>__awaiter(this,void 0,void 0,function*(){let path='/projects';let payload={};if(typeof search!=='undefined'){payload['search']=search;}
if(typeof limit!=='undefined'){payload['limit']=limit;}
if(typeof offset!=='undefined'){payload['offset']=offset;}
if(typeof after!=='undefined'){payload['after']=after;}
if(typeof orderType!=='undefined'){payload['orderType']=orderType;}
const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),create:(projectId,name,teamId,description,logo,url,legalName,legalCountry,legalState,legalCity,legalAddress,legalTaxId)=>__awaiter(this,void 0,void 0,function*(){if(typeof projectId==='undefined'){throw new AppwriteException('Missing required parameter: "projectId"');}
if(typeof name==='undefined'){throw new AppwriteException('Missing required parameter: "name"');}
@ -387,9 +395,10 @@ if(typeof httpUser!=='undefined'){payload['httpUser']=httpUser;}
if(typeof httpPass!=='undefined'){payload['httpPass']=httpPass;}
const uri=new URL(this.config.endpoint+path);return yield this.call('put',uri,{'content-type':'application/json',},payload);}),deleteWebhook:(projectId,webhookId)=>__awaiter(this,void 0,void 0,function*(){if(typeof projectId==='undefined'){throw new AppwriteException('Missing required parameter: "projectId"');}
if(typeof webhookId==='undefined'){throw new AppwriteException('Missing required parameter: "webhookId"');}
let path='/projects/{projectId}/webhooks/{webhookId}'.replace('{projectId}',projectId).replace('{webhookId}',webhookId);let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('delete',uri,{'content-type':'application/json',},payload);})};this.storage={listFiles:(search,limit,offset,orderType)=>__awaiter(this,void 0,void 0,function*(){let path='/storage/files';let payload={};if(typeof search!=='undefined'){payload['search']=search;}
let path='/projects/{projectId}/webhooks/{webhookId}'.replace('{projectId}',projectId).replace('{webhookId}',webhookId);let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('delete',uri,{'content-type':'application/json',},payload);})};this.storage={listFiles:(search,limit,offset,after,orderType)=>__awaiter(this,void 0,void 0,function*(){let path='/storage/files';let payload={};if(typeof search!=='undefined'){payload['search']=search;}
if(typeof limit!=='undefined'){payload['limit']=limit;}
if(typeof offset!=='undefined'){payload['offset']=offset;}
if(typeof after!=='undefined'){payload['after']=after;}
if(typeof orderType!=='undefined'){payload['orderType']=orderType;}
const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),createFile:(fileId,file,read,write)=>__awaiter(this,void 0,void 0,function*(){if(typeof fileId==='undefined'){throw new AppwriteException('Missing required parameter: "fileId"');}
if(typeof file==='undefined'){throw new AppwriteException('Missing required parameter: "file"');}
@ -421,9 +430,10 @@ if(typeof output!=='undefined'){payload['output']=output;}
const uri=new URL(this.config.endpoint+path);payload['project']=this.config.project;for(const[key,value]of Object.entries(this.flatten(payload))){uri.searchParams.append(key,value);}
return uri;},getFileView:(fileId)=>{if(typeof fileId==='undefined'){throw new AppwriteException('Missing required parameter: "fileId"');}
let path='/storage/files/{fileId}/view'.replace('{fileId}',fileId);let payload={};const uri=new URL(this.config.endpoint+path);payload['project']=this.config.project;for(const[key,value]of Object.entries(this.flatten(payload))){uri.searchParams.append(key,value);}
return uri;}};this.teams={list:(search,limit,offset,orderType)=>__awaiter(this,void 0,void 0,function*(){let path='/teams';let payload={};if(typeof search!=='undefined'){payload['search']=search;}
return uri;}};this.teams={list:(search,limit,offset,after,orderType)=>__awaiter(this,void 0,void 0,function*(){let path='/teams';let payload={};if(typeof search!=='undefined'){payload['search']=search;}
if(typeof limit!=='undefined'){payload['limit']=limit;}
if(typeof offset!=='undefined'){payload['offset']=offset;}
if(typeof after!=='undefined'){payload['after']=after;}
if(typeof orderType!=='undefined'){payload['orderType']=orderType;}
const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),create:(teamId,name,roles)=>__awaiter(this,void 0,void 0,function*(){if(typeof teamId==='undefined'){throw new AppwriteException('Missing required parameter: "teamId"');}
if(typeof name==='undefined'){throw new AppwriteException('Missing required parameter: "name"');}
@ -435,10 +445,11 @@ let path='/teams/{teamId}'.replace('{teamId}',teamId);let payload={};const uri=n
if(typeof name==='undefined'){throw new AppwriteException('Missing required parameter: "name"');}
let path='/teams/{teamId}'.replace('{teamId}',teamId);let payload={};if(typeof name!=='undefined'){payload['name']=name;}
const uri=new URL(this.config.endpoint+path);return yield this.call('put',uri,{'content-type':'application/json',},payload);}),delete:(teamId)=>__awaiter(this,void 0,void 0,function*(){if(typeof teamId==='undefined'){throw new AppwriteException('Missing required parameter: "teamId"');}
let path='/teams/{teamId}'.replace('{teamId}',teamId);let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('delete',uri,{'content-type':'application/json',},payload);}),getMemberships:(teamId,search,limit,offset,orderType)=>__awaiter(this,void 0,void 0,function*(){if(typeof teamId==='undefined'){throw new AppwriteException('Missing required parameter: "teamId"');}
let path='/teams/{teamId}'.replace('{teamId}',teamId);let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('delete',uri,{'content-type':'application/json',},payload);}),getMemberships:(teamId,search,limit,offset,after,orderType)=>__awaiter(this,void 0,void 0,function*(){if(typeof teamId==='undefined'){throw new AppwriteException('Missing required parameter: "teamId"');}
let path='/teams/{teamId}/memberships'.replace('{teamId}',teamId);let payload={};if(typeof search!=='undefined'){payload['search']=search;}
if(typeof limit!=='undefined'){payload['limit']=limit;}
if(typeof offset!=='undefined'){payload['offset']=offset;}
if(typeof after!=='undefined'){payload['after']=after;}
if(typeof orderType!=='undefined'){payload['orderType']=orderType;}
const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),createMembership:(teamId,email,roles,url,name)=>__awaiter(this,void 0,void 0,function*(){if(typeof teamId==='undefined'){throw new AppwriteException('Missing required parameter: "teamId"');}
if(typeof email==='undefined'){throw new AppwriteException('Missing required parameter: "email"');}
@ -462,9 +473,10 @@ if(typeof userId==='undefined'){throw new AppwriteException('Missing required pa
if(typeof secret==='undefined'){throw new AppwriteException('Missing required parameter: "secret"');}
let path='/teams/{teamId}/memberships/{membershipId}/status'.replace('{teamId}',teamId).replace('{membershipId}',membershipId);let payload={};if(typeof userId!=='undefined'){payload['userId']=userId;}
if(typeof secret!=='undefined'){payload['secret']=secret;}
const uri=new URL(this.config.endpoint+path);return yield this.call('patch',uri,{'content-type':'application/json',},payload);})};this.users={list:(search,limit,offset,orderType)=>__awaiter(this,void 0,void 0,function*(){let path='/users';let payload={};if(typeof search!=='undefined'){payload['search']=search;}
const uri=new URL(this.config.endpoint+path);return yield this.call('patch',uri,{'content-type':'application/json',},payload);})};this.users={list:(search,limit,offset,after,orderType)=>__awaiter(this,void 0,void 0,function*(){let path='/users';let payload={};if(typeof search!=='undefined'){payload['search']=search;}
if(typeof limit!=='undefined'){payload['limit']=limit;}
if(typeof offset!=='undefined'){payload['offset']=offset;}
if(typeof after!=='undefined'){payload['after']=after;}
if(typeof orderType!=='undefined'){payload['orderType']=orderType;}
const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),create:(userId,email,password,name)=>__awaiter(this,void 0,void 0,function*(){if(typeof userId==='undefined'){throw new AppwriteException('Missing required parameter: "userId"');}
if(typeof email==='undefined'){throw new AppwriteException('Missing required parameter: "email"');}
@ -475,8 +487,17 @@ if(typeof password!=='undefined'){payload['password']=password;}
if(typeof name!=='undefined'){payload['name']=name;}
const uri=new URL(this.config.endpoint+path);return yield this.call('post',uri,{'content-type':'application/json',},payload);}),get:(userId)=>__awaiter(this,void 0,void 0,function*(){if(typeof userId==='undefined'){throw new AppwriteException('Missing required parameter: "userId"');}
let path='/users/{userId}'.replace('{userId}',userId);let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),delete:(userId)=>__awaiter(this,void 0,void 0,function*(){if(typeof userId==='undefined'){throw new AppwriteException('Missing required parameter: "userId"');}
let path='/users/{userId}'.replace('{userId}',userId);let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('delete',uri,{'content-type':'application/json',},payload);}),getLogs:(userId)=>__awaiter(this,void 0,void 0,function*(){if(typeof userId==='undefined'){throw new AppwriteException('Missing required parameter: "userId"');}
let path='/users/{userId}/logs'.replace('{userId}',userId);let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),getPrefs:(userId)=>__awaiter(this,void 0,void 0,function*(){if(typeof userId==='undefined'){throw new AppwriteException('Missing required parameter: "userId"');}
let path='/users/{userId}'.replace('{userId}',userId);let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('delete',uri,{'content-type':'application/json',},payload);}),updateEmail:(userId,email)=>__awaiter(this,void 0,void 0,function*(){if(typeof userId==='undefined'){throw new AppwriteException('Missing required parameter: "userId"');}
if(typeof email==='undefined'){throw new AppwriteException('Missing required parameter: "email"');}
let path='/users/{userId}/email'.replace('{userId}',userId);let payload={};if(typeof email!=='undefined'){payload['email']=email;}
const uri=new URL(this.config.endpoint+path);return yield this.call('patch',uri,{'content-type':'application/json',},payload);}),getLogs:(userId)=>__awaiter(this,void 0,void 0,function*(){if(typeof userId==='undefined'){throw new AppwriteException('Missing required parameter: "userId"');}
let path='/users/{userId}/logs'.replace('{userId}',userId);let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),updateName:(userId,name)=>__awaiter(this,void 0,void 0,function*(){if(typeof userId==='undefined'){throw new AppwriteException('Missing required parameter: "userId"');}
if(typeof name==='undefined'){throw new AppwriteException('Missing required parameter: "name"');}
let path='/users/{userId}/name'.replace('{userId}',userId);let payload={};if(typeof name!=='undefined'){payload['name']=name;}
const uri=new URL(this.config.endpoint+path);return yield this.call('patch',uri,{'content-type':'application/json',},payload);}),updatePassword:(userId,password)=>__awaiter(this,void 0,void 0,function*(){if(typeof userId==='undefined'){throw new AppwriteException('Missing required parameter: "userId"');}
if(typeof password==='undefined'){throw new AppwriteException('Missing required parameter: "password"');}
let path='/users/{userId}/password'.replace('{userId}',userId);let payload={};if(typeof password!=='undefined'){payload['password']=password;}
const uri=new URL(this.config.endpoint+path);return yield this.call('patch',uri,{'content-type':'application/json',},payload);}),getPrefs:(userId)=>__awaiter(this,void 0,void 0,function*(){if(typeof userId==='undefined'){throw new AppwriteException('Missing required parameter: "userId"');}
let path='/users/{userId}/prefs'.replace('{userId}',userId);let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),updatePrefs:(userId,prefs)=>__awaiter(this,void 0,void 0,function*(){if(typeof userId==='undefined'){throw new AppwriteException('Missing required parameter: "userId"');}
if(typeof prefs==='undefined'){throw new AppwriteException('Missing required parameter: "prefs"');}
let path='/users/{userId}/prefs'.replace('{userId}',userId);let payload={};if(typeof prefs!=='undefined'){payload['prefs']=prefs;}
@ -2128,7 +2149,7 @@ result+'")": '+
error);}
if(debug){console.info("debug-ls-if result:",result);}
paths=expression.getPaths();let prv=element.$lsSkip;element.$lsSkip=!result;if(!result){element.style.visibility="hidden";element.style.display="none";}else{element.style.removeProperty("display");element.style.removeProperty("visibility");}
if(prv===true&&element.$lsSkip===false){view.render(element);}};check();for(let i=0;i<paths.length;i++){let path=paths[i].split(".");while(path.length){container.bind(element,path.join("."),check);path.pop();}}},});window.ls.container.get("view").add({selector:"data-ls-loop",template:false,nested:false,controller:function(element,view,container,filter,window,expression){let expr=expression.parse(element.getAttribute("data-ls-loop"));let as=element.getAttribute("data-ls-as");let filterName=element.getAttribute("data-ls-filter");let key=element.getAttribute("data-ls-key")||"$index";let prefix=element.getAttribute("data-ls-prefix")||null;let postfix=element.getAttribute("data-ls-postfix")||null;let limit=parseInt(expression.parse(element.getAttribute("data-ls-limit")||"")||-1);let debug=element.getAttribute("data-debug")||false;let echo=function(){let array=container.path(expr);let counter=0;array=!array?[]:array;if(filterName){array=filter.apply(filterName,array);console.log(array);}
if(prv===true&&element.$lsSkip===false){view.render(element);}};check();for(let i=0;i<paths.length;i++){let path=paths[i].split(".");while(path.length){container.bind(element,path.join("."),check);path.pop();}}},});window.ls.container.get("view").add({selector:"data-ls-loop",template:false,nested:false,controller:function(element,view,container,filter,window,expression){let expr=expression.parse(element.getAttribute("data-ls-loop"));let as=element.getAttribute("data-ls-as");let filterName=element.getAttribute("data-ls-filter");let key=element.getAttribute("data-ls-key")||"$index";let prefix=element.getAttribute("data-ls-prefix")||null;let postfix=element.getAttribute("data-ls-postfix")||null;let limit=parseInt(expression.parse(element.getAttribute("data-ls-limit")||"")||-1);let debug=element.getAttribute("data-debug")||false;let echo=function(){let array=container.path(expr);let counter=0;array=!array?[]:array;if(filterName){array=filter.apply(filterName,array);}
let watch=!!(array&&array.__proxy);while(element.hasChildNodes()){element.removeChild(element.lastChild);element.lastChild=null;}
if(array instanceof Array&&typeof array!=="object"){throw new Error("Reference value must be array or object. "+typeof array+" given");}
let children=[];element.$lsSkip=true;element.style.visibility=0===array.length&&element.style.visibility==""?"hidden":"visible";if(prefix){prefixElement=document.getElementById(prefix);let html=prefixElement.innerHTML;prefixElement=template.cloneNode(true);if(prefixElement){prefixElement.innerHTML=html;element.appendChild(prefixElement);view.render(prefixElement);}}
@ -2313,7 +2334,7 @@ return'';}).add("runtimeLogo",function($value,env){if(env&&env.RUNTIMES&&env.RUN
return'';}).add("runtimeVersion",function($value,env){if(env&&env.RUNTIMES&&env.RUNTIMES[$value]){return env.RUNTIMES[$value].version;}
return'';}).add("indexAttributes",function($value){let output='';for(let i=0;i<$value.attributes.length;i++){output+=$value.attributes[i]+' ('+$value.orders[i]+'), '}
return output.slice(0,-2);}).add("collectionAttributes",function($value){if(!Array.isArray($value)){return[];}
$value.unshift({$id:'$id'});return $value;}).add("documentAttribute",function($value,attribute){if($value[attribute.$id]){return $value[attribute.$id];}
$value.unshift({$id:'$id'});return $value;}).add("documentAttribute",function($value,attribute){if($value[attribute.key]){return $value[attribute.key];}
return null;});function abbreviate(number,maxPlaces,forcePlaces,forceLetter){number=Number(number);forceLetter=forceLetter||false;if(forceLetter!==false){return annotate(number,maxPlaces,forcePlaces,forceLetter);}
let abbr;if(number>=1e12){abbr="T";}else if(number>=1e9){abbr="B";}else if(number>=1e6){abbr="M";}else if(number>=1e3){abbr="K";}else{abbr="";}
return annotate(number,maxPlaces,forcePlaces,abbr);}
@ -2391,8 +2412,8 @@ element.setAttribute("data-id-type",idType);info.innerHTML="Appwrite will genera
button.className=idType=="custom"?"icon-shuffle copy":"icon-edit copy";}
const syncEditorWithID=function(event){if(element.value!=='unique()'||idType!='auto'){writer.value=element.value;}
if(idType=='auto'){element.value='unique()';}}
const keypress=function(e){const key=e.which||e.keyCode;const ZERO=48;const NINE=57;const SMALL_A=97;const SMALL_Z=122;const CAPITAL_A=65;const CAPITAL_Z=90;const UNDERSCORE=95;const isNotValidDigit=key<ZERO||key>NINE;const isNotValidSmallAlphabet=key<SMALL_A||key>SMALL_Z;const isNotValidCapitalAlphabet=key<CAPITAL_A||key>CAPITAL_Z;if(key==UNDERSCORE&&e.target.value.length==0){e.preventDefault();}
if(key!=UNDERSCORE&&isNotValidDigit&&isNotValidSmallAlphabet&&isNotValidCapitalAlphabet){e.preventDefault();}}
const keypress=function(e){const key=e.which||e.keyCode;const ZERO=48;const NINE=57;const SMALL_A=97;const SMALL_Z=122;const CAPITAL_A=65;const CAPITAL_Z=90;const UNDERSCORE=95;const HYPHEN=45;const PERIOD=46;const isNotValidDigit=key<ZERO||key>NINE;const isNotValidSmallAlphabet=key<SMALL_A||key>SMALL_Z;const isNotValidCapitalAlphabet=key<CAPITAL_A||key>CAPITAL_Z;const isNotValidFirstChar=(key===UNDERSCORE||key===HYPHEN||key===PERIOD);if(isNotValidFirstChar&&e.target.value.length==0){e.preventDefault();}
if(key!=UNDERSCORE&&key!=HYPHEN&&key!=PERIOD&&isNotValidDigit&&isNotValidSmallAlphabet&&isNotValidCapitalAlphabet){e.preventDefault();}}
syncEditorWithID();setIdType(idType);writer.addEventListener("change",function(event){element.value=writer.value;});writer.form.addEventListener('reset',function(event){const resetEvent=new Event('reset');element.dispatchEvent(resetEvent);});element.addEventListener('reset',function(event){idType=element.getAttribute('data-id-type');setIdType(idType);});writer.addEventListener('keypress',keypress);button.addEventListener("click",switchType);}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-document",controller:function(element,container,search){var formsDocument=(element.dataset["formsDocument"]||'');var searchButton=(element.dataset["search"]||0);let path=container.scope(searchButton);element.addEventListener('click',function(){search.selected=element.value;search.path=path;document.dispatchEvent(new CustomEvent(formsDocument,{bubbles:false,cancelable:true}));});}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-duplications",controller:function(element){let validate=function(element){let duplication=0;let form=element.form;for(let i=0;i<form.elements.length;i++){let field=form.elements[i];if(field.name===element.name&&field.value===element.value){duplication++;}}
if(duplication>1){element.setCustomValidity("Duplicated value");}
else{element.setCustomValidity("");}};element.addEventListener('change',function(event){validate(event.target)});element.addEventListener('focus',function(event){validate(event.target)});element.addEventListener('blur',function(event){validate(event.target)});}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-document-preview",controller:function(element,container,search){element.addEventListener('change',function(){console.log(element.value);});}});})(window);(function(window){window.ls.container.get("view").add({selector:"data-forms-filter",controller:function(document,container,expression,element,form,di){let name=element.dataset["formsFilter"]||"";let events=element.dataset["event"]||"";let serialize=function(obj,prefix){let str=[],p;for(p in obj){if(obj.hasOwnProperty(p)){let k=prefix?prefix+"["+p+"]":p,v=obj[p];if(v===""){continue;}

View file

@ -88,22 +88,27 @@ if(typeof size!=='undefined'){payload['size']=size;}
if(typeof margin!=='undefined'){payload['margin']=margin;}
if(typeof download!=='undefined'){payload['download']=download;}
const uri=new URL(this.config.endpoint+path);payload['project']=this.config.project;for(const[key,value]of Object.entries(this.flatten(payload))){uri.searchParams.append(key,value);}
return uri;}};this.database={listCollections:(search,limit,offset,orderType)=>__awaiter(this,void 0,void 0,function*(){let path='/database/collections';let payload={};if(typeof search!=='undefined'){payload['search']=search;}
return uri;}};this.database={listCollections:(search,limit,offset,after,orderType)=>__awaiter(this,void 0,void 0,function*(){let path='/database/collections';let payload={};if(typeof search!=='undefined'){payload['search']=search;}
if(typeof limit!=='undefined'){payload['limit']=limit;}
if(typeof offset!=='undefined'){payload['offset']=offset;}
if(typeof after!=='undefined'){payload['after']=after;}
if(typeof orderType!=='undefined'){payload['orderType']=orderType;}
const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),createCollection:(collectionId,name,read,write)=>__awaiter(this,void 0,void 0,function*(){if(typeof collectionId==='undefined'){throw new AppwriteException('Missing required parameter: "collectionId"');}
const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),createCollection:(collectionId,name,permission,read,write)=>__awaiter(this,void 0,void 0,function*(){if(typeof collectionId==='undefined'){throw new AppwriteException('Missing required parameter: "collectionId"');}
if(typeof name==='undefined'){throw new AppwriteException('Missing required parameter: "name"');}
if(typeof permission==='undefined'){throw new AppwriteException('Missing required parameter: "permission"');}
if(typeof read==='undefined'){throw new AppwriteException('Missing required parameter: "read"');}
if(typeof write==='undefined'){throw new AppwriteException('Missing required parameter: "write"');}
let path='/database/collections';let payload={};if(typeof collectionId!=='undefined'){payload['collectionId']=collectionId;}
if(typeof name!=='undefined'){payload['name']=name;}
if(typeof permission!=='undefined'){payload['permission']=permission;}
if(typeof read!=='undefined'){payload['read']=read;}
if(typeof write!=='undefined'){payload['write']=write;}
const uri=new URL(this.config.endpoint+path);return yield this.call('post',uri,{'content-type':'application/json',},payload);}),getCollection:(collectionId)=>__awaiter(this,void 0,void 0,function*(){if(typeof collectionId==='undefined'){throw new AppwriteException('Missing required parameter: "collectionId"');}
let path='/database/collections/{collectionId}'.replace('{collectionId}',collectionId);let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),updateCollection:(collectionId,name,read,write)=>__awaiter(this,void 0,void 0,function*(){if(typeof collectionId==='undefined'){throw new AppwriteException('Missing required parameter: "collectionId"');}
let path='/database/collections/{collectionId}'.replace('{collectionId}',collectionId);let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),updateCollection:(collectionId,name,permission,read,write)=>__awaiter(this,void 0,void 0,function*(){if(typeof collectionId==='undefined'){throw new AppwriteException('Missing required parameter: "collectionId"');}
if(typeof name==='undefined'){throw new AppwriteException('Missing required parameter: "name"');}
if(typeof permission==='undefined'){throw new AppwriteException('Missing required parameter: "permission"');}
let path='/database/collections/{collectionId}'.replace('{collectionId}',collectionId);let payload={};if(typeof name!=='undefined'){payload['name']=name;}
if(typeof permission!=='undefined'){payload['permission']=permission;}
if(typeof read!=='undefined'){payload['read']=read;}
if(typeof write!=='undefined'){payload['write']=write;}
const uri=new URL(this.config.endpoint+path);return yield this.call('put',uri,{'content-type':'application/json',},payload);}),deleteCollection:(collectionId)=>__awaiter(this,void 0,void 0,function*(){if(typeof collectionId==='undefined'){throw new AppwriteException('Missing required parameter: "collectionId"');}
@ -156,12 +161,10 @@ if(typeof size!=='undefined'){payload['size']=size;}
if(typeof required!=='undefined'){payload['required']=required;}
if(typeof xdefault!=='undefined'){payload['xdefault']=xdefault;}
if(typeof array!=='undefined'){payload['array']=array;}
const uri=new URL(this.config.endpoint+path);return yield this.call('post',uri,{'content-type':'application/json',},payload);}),createUrlAttribute:(collectionId,attributeId,size,required,xdefault,array)=>__awaiter(this,void 0,void 0,function*(){if(typeof collectionId==='undefined'){throw new AppwriteException('Missing required parameter: "collectionId"');}
const uri=new URL(this.config.endpoint+path);return yield this.call('post',uri,{'content-type':'application/json',},payload);}),createUrlAttribute:(collectionId,attributeId,required,xdefault,array)=>__awaiter(this,void 0,void 0,function*(){if(typeof collectionId==='undefined'){throw new AppwriteException('Missing required parameter: "collectionId"');}
if(typeof attributeId==='undefined'){throw new AppwriteException('Missing required parameter: "attributeId"');}
if(typeof size==='undefined'){throw new AppwriteException('Missing required parameter: "size"');}
if(typeof required==='undefined'){throw new AppwriteException('Missing required parameter: "required"');}
let path='/database/collections/{collectionId}/attributes/url'.replace('{collectionId}',collectionId);let payload={};if(typeof attributeId!=='undefined'){payload['attributeId']=attributeId;}
if(typeof size!=='undefined'){payload['size']=size;}
if(typeof required!=='undefined'){payload['required']=required;}
if(typeof xdefault!=='undefined'){payload['xdefault']=xdefault;}
if(typeof array!=='undefined'){payload['array']=array;}
@ -169,10 +172,11 @@ const uri=new URL(this.config.endpoint+path);return yield this.call('post',uri,{
if(typeof attributeId==='undefined'){throw new AppwriteException('Missing required parameter: "attributeId"');}
let path='/database/collections/{collectionId}/attributes/{attributeId}'.replace('{collectionId}',collectionId).replace('{attributeId}',attributeId);let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),deleteAttribute:(collectionId,attributeId)=>__awaiter(this,void 0,void 0,function*(){if(typeof collectionId==='undefined'){throw new AppwriteException('Missing required parameter: "collectionId"');}
if(typeof attributeId==='undefined'){throw new AppwriteException('Missing required parameter: "attributeId"');}
let path='/database/collections/{collectionId}/attributes/{attributeId}'.replace('{collectionId}',collectionId).replace('{attributeId}',attributeId);let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('delete',uri,{'content-type':'application/json',},payload);}),listDocuments:(collectionId,queries,limit,offset,orderAttributes,orderTypes)=>__awaiter(this,void 0,void 0,function*(){if(typeof collectionId==='undefined'){throw new AppwriteException('Missing required parameter: "collectionId"');}
let path='/database/collections/{collectionId}/attributes/{attributeId}'.replace('{collectionId}',collectionId).replace('{attributeId}',attributeId);let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('delete',uri,{'content-type':'application/json',},payload);}),listDocuments:(collectionId,queries,limit,offset,after,orderAttributes,orderTypes)=>__awaiter(this,void 0,void 0,function*(){if(typeof collectionId==='undefined'){throw new AppwriteException('Missing required parameter: "collectionId"');}
let path='/database/collections/{collectionId}/documents'.replace('{collectionId}',collectionId);let payload={};if(typeof queries!=='undefined'){payload['queries']=queries;}
if(typeof limit!=='undefined'){payload['limit']=limit;}
if(typeof offset!=='undefined'){payload['offset']=offset;}
if(typeof after!=='undefined'){payload['after']=after;}
if(typeof orderAttributes!=='undefined'){payload['orderAttributes']=orderAttributes;}
if(typeof orderTypes!=='undefined'){payload['orderTypes']=orderTypes;}
const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),createDocument:(collectionId,documentId,data,read,write)=>__awaiter(this,void 0,void 0,function*(){if(typeof collectionId==='undefined'){throw new AppwriteException('Missing required parameter: "collectionId"');}
@ -201,14 +205,15 @@ let path='/database/collections/{collectionId}/indexes'.replace('{collectionId}'
if(typeof type!=='undefined'){payload['type']=type;}
if(typeof attributes!=='undefined'){payload['attributes']=attributes;}
if(typeof orders!=='undefined'){payload['orders']=orders;}
console.log(collectionId,indexId,type,attributes,orders);const uri=new URL(this.config.endpoint+path);return yield this.call('post',uri,{'content-type':'application/json',},payload);}),getIndex:(collectionId,indexId)=>__awaiter(this,void 0,void 0,function*(){if(typeof collectionId==='undefined'){throw new AppwriteException('Missing required parameter: "collectionId"');}
const uri=new URL(this.config.endpoint+path);return yield this.call('post',uri,{'content-type':'application/json',},payload);}),getIndex:(collectionId,indexId)=>__awaiter(this,void 0,void 0,function*(){if(typeof collectionId==='undefined'){throw new AppwriteException('Missing required parameter: "collectionId"');}
if(typeof indexId==='undefined'){throw new AppwriteException('Missing required parameter: "indexId"');}
let path='/database/collections/{collectionId}/indexes/{indexId}'.replace('{collectionId}',collectionId).replace('{indexId}',indexId);let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),deleteIndex:(collectionId,indexId)=>__awaiter(this,void 0,void 0,function*(){if(typeof collectionId==='undefined'){throw new AppwriteException('Missing required parameter: "collectionId"');}
if(typeof indexId==='undefined'){throw new AppwriteException('Missing required parameter: "indexId"');}
let path='/database/collections/{collectionId}/indexes/{indexId}'.replace('{collectionId}',collectionId).replace('{indexId}',indexId);let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('delete',uri,{'content-type':'application/json',},payload);}),listCollectionLogs:(collectionId)=>__awaiter(this,void 0,void 0,function*(){if(typeof collectionId==='undefined'){throw new AppwriteException('Missing required parameter: "collectionId"');}
let path='/database/collections/{collectionId}/logs'.replace('{collectionId}',collectionId);let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);})};this.functions={list:(search,limit,offset,orderType)=>__awaiter(this,void 0,void 0,function*(){let path='/functions';let payload={};if(typeof search!=='undefined'){payload['search']=search;}
let path='/database/collections/{collectionId}/logs'.replace('{collectionId}',collectionId);let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);})};this.functions={list:(search,limit,offset,after,orderType)=>__awaiter(this,void 0,void 0,function*(){let path='/functions';let payload={};if(typeof search!=='undefined'){payload['search']=search;}
if(typeof limit!=='undefined'){payload['limit']=limit;}
if(typeof offset!=='undefined'){payload['offset']=offset;}
if(typeof after!=='undefined'){payload['after']=after;}
if(typeof orderType!=='undefined'){payload['orderType']=orderType;}
const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),create:(functionId,name,execute,runtime,vars,events,schedule,timeout)=>__awaiter(this,void 0,void 0,function*(){if(typeof functionId==='undefined'){throw new AppwriteException('Missing required parameter: "functionId"');}
if(typeof name==='undefined'){throw new AppwriteException('Missing required parameter: "name"');}
@ -233,9 +238,10 @@ if(typeof events!=='undefined'){payload['events']=events;}
if(typeof schedule!=='undefined'){payload['schedule']=schedule;}
if(typeof timeout!=='undefined'){payload['timeout']=timeout;}
const uri=new URL(this.config.endpoint+path);return yield this.call('put',uri,{'content-type':'application/json',},payload);}),delete:(functionId)=>__awaiter(this,void 0,void 0,function*(){if(typeof functionId==='undefined'){throw new AppwriteException('Missing required parameter: "functionId"');}
let path='/functions/{functionId}'.replace('{functionId}',functionId);let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('delete',uri,{'content-type':'application/json',},payload);}),listExecutions:(functionId,limit,offset)=>__awaiter(this,void 0,void 0,function*(){if(typeof functionId==='undefined'){throw new AppwriteException('Missing required parameter: "functionId"');}
let path='/functions/{functionId}'.replace('{functionId}',functionId);let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('delete',uri,{'content-type':'application/json',},payload);}),listExecutions:(functionId,limit,offset,after)=>__awaiter(this,void 0,void 0,function*(){if(typeof functionId==='undefined'){throw new AppwriteException('Missing required parameter: "functionId"');}
let path='/functions/{functionId}/executions'.replace('{functionId}',functionId);let payload={};if(typeof limit!=='undefined'){payload['limit']=limit;}
if(typeof offset!=='undefined'){payload['offset']=offset;}
if(typeof after!=='undefined'){payload['after']=after;}
const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),createExecution:(functionId,data)=>__awaiter(this,void 0,void 0,function*(){if(typeof functionId==='undefined'){throw new AppwriteException('Missing required parameter: "functionId"');}
let path='/functions/{functionId}/executions'.replace('{functionId}',functionId);let payload={};if(typeof data!=='undefined'){payload['data']=data;}
const uri=new URL(this.config.endpoint+path);return yield this.call('post',uri,{'content-type':'application/json',},payload);}),getExecution:(functionId,executionId)=>__awaiter(this,void 0,void 0,function*(){if(typeof functionId==='undefined'){throw new AppwriteException('Missing required parameter: "functionId"');}
@ -243,10 +249,11 @@ if(typeof executionId==='undefined'){throw new AppwriteException('Missing requir
let path='/functions/{functionId}/executions/{executionId}'.replace('{functionId}',functionId).replace('{executionId}',executionId);let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),updateTag:(functionId,tag)=>__awaiter(this,void 0,void 0,function*(){if(typeof functionId==='undefined'){throw new AppwriteException('Missing required parameter: "functionId"');}
if(typeof tag==='undefined'){throw new AppwriteException('Missing required parameter: "tag"');}
let path='/functions/{functionId}/tag'.replace('{functionId}',functionId);let payload={};if(typeof tag!=='undefined'){payload['tag']=tag;}
const uri=new URL(this.config.endpoint+path);return yield this.call('patch',uri,{'content-type':'application/json',},payload);}),listTags:(functionId,search,limit,offset,orderType)=>__awaiter(this,void 0,void 0,function*(){if(typeof functionId==='undefined'){throw new AppwriteException('Missing required parameter: "functionId"');}
const uri=new URL(this.config.endpoint+path);return yield this.call('patch',uri,{'content-type':'application/json',},payload);}),listTags:(functionId,search,limit,offset,after,orderType)=>__awaiter(this,void 0,void 0,function*(){if(typeof functionId==='undefined'){throw new AppwriteException('Missing required parameter: "functionId"');}
let path='/functions/{functionId}/tags'.replace('{functionId}',functionId);let payload={};if(typeof search!=='undefined'){payload['search']=search;}
if(typeof limit!=='undefined'){payload['limit']=limit;}
if(typeof offset!=='undefined'){payload['offset']=offset;}
if(typeof after!=='undefined'){payload['after']=after;}
if(typeof orderType!=='undefined'){payload['orderType']=orderType;}
const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),createTag:(functionId,command,code)=>__awaiter(this,void 0,void 0,function*(){if(typeof functionId==='undefined'){throw new AppwriteException('Missing required parameter: "functionId"');}
if(typeof command==='undefined'){throw new AppwriteException('Missing required parameter: "command"');}
@ -259,9 +266,10 @@ let path='/functions/{functionId}/tags/{tagId}'.replace('{functionId}',functionI
if(typeof tagId==='undefined'){throw new AppwriteException('Missing required parameter: "tagId"');}
let path='/functions/{functionId}/tags/{tagId}'.replace('{functionId}',functionId).replace('{tagId}',tagId);let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('delete',uri,{'content-type':'application/json',},payload);}),getUsage:(functionId,range)=>__awaiter(this,void 0,void 0,function*(){if(typeof functionId==='undefined'){throw new AppwriteException('Missing required parameter: "functionId"');}
let path='/functions/{functionId}/usage'.replace('{functionId}',functionId);let payload={};if(typeof range!=='undefined'){payload['range']=range;}
const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);})};this.health={get:()=>__awaiter(this,void 0,void 0,function*(){let path='/health';let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),getAntiVirus:()=>__awaiter(this,void 0,void 0,function*(){let path='/health/anti-virus';let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),getCache:()=>__awaiter(this,void 0,void 0,function*(){let path='/health/cache';let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),getDB:()=>__awaiter(this,void 0,void 0,function*(){let path='/health/db';let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),getQueueCertificates:()=>__awaiter(this,void 0,void 0,function*(){let path='/health/queue/certificates';let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),getQueueFunctions:()=>__awaiter(this,void 0,void 0,function*(){let path='/health/queue/functions';let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),getQueueLogs:()=>__awaiter(this,void 0,void 0,function*(){let path='/health/queue/logs';let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),getQueueUsage:()=>__awaiter(this,void 0,void 0,function*(){let path='/health/queue/usage';let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),getQueueWebhooks:()=>__awaiter(this,void 0,void 0,function*(){let path='/health/queue/webhooks';let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),getStorageLocal:()=>__awaiter(this,void 0,void 0,function*(){let path='/health/storage/local';let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),getTime:()=>__awaiter(this,void 0,void 0,function*(){let path='/health/time';let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);})};this.locale={get:()=>__awaiter(this,void 0,void 0,function*(){let path='/locale';let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),getContinents:()=>__awaiter(this,void 0,void 0,function*(){let path='/locale/continents';let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),getCountries:()=>__awaiter(this,void 0,void 0,function*(){let path='/locale/countries';let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),getCountriesEU:()=>__awaiter(this,void 0,void 0,function*(){let path='/locale/countries/eu';let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),getCountriesPhones:()=>__awaiter(this,void 0,void 0,function*(){let path='/locale/countries/phones';let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),getCurrencies:()=>__awaiter(this,void 0,void 0,function*(){let path='/locale/currencies';let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),getLanguages:()=>__awaiter(this,void 0,void 0,function*(){let path='/locale/languages';let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);})};this.projects={list:(search,limit,offset,orderType)=>__awaiter(this,void 0,void 0,function*(){let path='/projects';let payload={};if(typeof search!=='undefined'){payload['search']=search;}
const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);})};this.health={get:()=>__awaiter(this,void 0,void 0,function*(){let path='/health';let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),getAntiVirus:()=>__awaiter(this,void 0,void 0,function*(){let path='/health/anti-virus';let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),getCache:()=>__awaiter(this,void 0,void 0,function*(){let path='/health/cache';let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),getDB:()=>__awaiter(this,void 0,void 0,function*(){let path='/health/db';let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),getQueueCertificates:()=>__awaiter(this,void 0,void 0,function*(){let path='/health/queue/certificates';let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),getQueueFunctions:()=>__awaiter(this,void 0,void 0,function*(){let path='/health/queue/functions';let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),getQueueLogs:()=>__awaiter(this,void 0,void 0,function*(){let path='/health/queue/logs';let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),getQueueUsage:()=>__awaiter(this,void 0,void 0,function*(){let path='/health/queue/usage';let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),getQueueWebhooks:()=>__awaiter(this,void 0,void 0,function*(){let path='/health/queue/webhooks';let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),getStorageLocal:()=>__awaiter(this,void 0,void 0,function*(){let path='/health/storage/local';let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),getTime:()=>__awaiter(this,void 0,void 0,function*(){let path='/health/time';let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);})};this.locale={get:()=>__awaiter(this,void 0,void 0,function*(){let path='/locale';let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),getContinents:()=>__awaiter(this,void 0,void 0,function*(){let path='/locale/continents';let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),getCountries:()=>__awaiter(this,void 0,void 0,function*(){let path='/locale/countries';let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),getCountriesEU:()=>__awaiter(this,void 0,void 0,function*(){let path='/locale/countries/eu';let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),getCountriesPhones:()=>__awaiter(this,void 0,void 0,function*(){let path='/locale/countries/phones';let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),getCurrencies:()=>__awaiter(this,void 0,void 0,function*(){let path='/locale/currencies';let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),getLanguages:()=>__awaiter(this,void 0,void 0,function*(){let path='/locale/languages';let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);})};this.projects={list:(search,limit,offset,after,orderType)=>__awaiter(this,void 0,void 0,function*(){let path='/projects';let payload={};if(typeof search!=='undefined'){payload['search']=search;}
if(typeof limit!=='undefined'){payload['limit']=limit;}
if(typeof offset!=='undefined'){payload['offset']=offset;}
if(typeof after!=='undefined'){payload['after']=after;}
if(typeof orderType!=='undefined'){payload['orderType']=orderType;}
const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),create:(projectId,name,teamId,description,logo,url,legalName,legalCountry,legalState,legalCity,legalAddress,legalTaxId)=>__awaiter(this,void 0,void 0,function*(){if(typeof projectId==='undefined'){throw new AppwriteException('Missing required parameter: "projectId"');}
if(typeof name==='undefined'){throw new AppwriteException('Missing required parameter: "name"');}
@ -387,9 +395,10 @@ if(typeof httpUser!=='undefined'){payload['httpUser']=httpUser;}
if(typeof httpPass!=='undefined'){payload['httpPass']=httpPass;}
const uri=new URL(this.config.endpoint+path);return yield this.call('put',uri,{'content-type':'application/json',},payload);}),deleteWebhook:(projectId,webhookId)=>__awaiter(this,void 0,void 0,function*(){if(typeof projectId==='undefined'){throw new AppwriteException('Missing required parameter: "projectId"');}
if(typeof webhookId==='undefined'){throw new AppwriteException('Missing required parameter: "webhookId"');}
let path='/projects/{projectId}/webhooks/{webhookId}'.replace('{projectId}',projectId).replace('{webhookId}',webhookId);let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('delete',uri,{'content-type':'application/json',},payload);})};this.storage={listFiles:(search,limit,offset,orderType)=>__awaiter(this,void 0,void 0,function*(){let path='/storage/files';let payload={};if(typeof search!=='undefined'){payload['search']=search;}
let path='/projects/{projectId}/webhooks/{webhookId}'.replace('{projectId}',projectId).replace('{webhookId}',webhookId);let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('delete',uri,{'content-type':'application/json',},payload);})};this.storage={listFiles:(search,limit,offset,after,orderType)=>__awaiter(this,void 0,void 0,function*(){let path='/storage/files';let payload={};if(typeof search!=='undefined'){payload['search']=search;}
if(typeof limit!=='undefined'){payload['limit']=limit;}
if(typeof offset!=='undefined'){payload['offset']=offset;}
if(typeof after!=='undefined'){payload['after']=after;}
if(typeof orderType!=='undefined'){payload['orderType']=orderType;}
const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),createFile:(fileId,file,read,write)=>__awaiter(this,void 0,void 0,function*(){if(typeof fileId==='undefined'){throw new AppwriteException('Missing required parameter: "fileId"');}
if(typeof file==='undefined'){throw new AppwriteException('Missing required parameter: "file"');}
@ -421,9 +430,10 @@ if(typeof output!=='undefined'){payload['output']=output;}
const uri=new URL(this.config.endpoint+path);payload['project']=this.config.project;for(const[key,value]of Object.entries(this.flatten(payload))){uri.searchParams.append(key,value);}
return uri;},getFileView:(fileId)=>{if(typeof fileId==='undefined'){throw new AppwriteException('Missing required parameter: "fileId"');}
let path='/storage/files/{fileId}/view'.replace('{fileId}',fileId);let payload={};const uri=new URL(this.config.endpoint+path);payload['project']=this.config.project;for(const[key,value]of Object.entries(this.flatten(payload))){uri.searchParams.append(key,value);}
return uri;}};this.teams={list:(search,limit,offset,orderType)=>__awaiter(this,void 0,void 0,function*(){let path='/teams';let payload={};if(typeof search!=='undefined'){payload['search']=search;}
return uri;}};this.teams={list:(search,limit,offset,after,orderType)=>__awaiter(this,void 0,void 0,function*(){let path='/teams';let payload={};if(typeof search!=='undefined'){payload['search']=search;}
if(typeof limit!=='undefined'){payload['limit']=limit;}
if(typeof offset!=='undefined'){payload['offset']=offset;}
if(typeof after!=='undefined'){payload['after']=after;}
if(typeof orderType!=='undefined'){payload['orderType']=orderType;}
const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),create:(teamId,name,roles)=>__awaiter(this,void 0,void 0,function*(){if(typeof teamId==='undefined'){throw new AppwriteException('Missing required parameter: "teamId"');}
if(typeof name==='undefined'){throw new AppwriteException('Missing required parameter: "name"');}
@ -435,10 +445,11 @@ let path='/teams/{teamId}'.replace('{teamId}',teamId);let payload={};const uri=n
if(typeof name==='undefined'){throw new AppwriteException('Missing required parameter: "name"');}
let path='/teams/{teamId}'.replace('{teamId}',teamId);let payload={};if(typeof name!=='undefined'){payload['name']=name;}
const uri=new URL(this.config.endpoint+path);return yield this.call('put',uri,{'content-type':'application/json',},payload);}),delete:(teamId)=>__awaiter(this,void 0,void 0,function*(){if(typeof teamId==='undefined'){throw new AppwriteException('Missing required parameter: "teamId"');}
let path='/teams/{teamId}'.replace('{teamId}',teamId);let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('delete',uri,{'content-type':'application/json',},payload);}),getMemberships:(teamId,search,limit,offset,orderType)=>__awaiter(this,void 0,void 0,function*(){if(typeof teamId==='undefined'){throw new AppwriteException('Missing required parameter: "teamId"');}
let path='/teams/{teamId}'.replace('{teamId}',teamId);let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('delete',uri,{'content-type':'application/json',},payload);}),getMemberships:(teamId,search,limit,offset,after,orderType)=>__awaiter(this,void 0,void 0,function*(){if(typeof teamId==='undefined'){throw new AppwriteException('Missing required parameter: "teamId"');}
let path='/teams/{teamId}/memberships'.replace('{teamId}',teamId);let payload={};if(typeof search!=='undefined'){payload['search']=search;}
if(typeof limit!=='undefined'){payload['limit']=limit;}
if(typeof offset!=='undefined'){payload['offset']=offset;}
if(typeof after!=='undefined'){payload['after']=after;}
if(typeof orderType!=='undefined'){payload['orderType']=orderType;}
const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),createMembership:(teamId,email,roles,url,name)=>__awaiter(this,void 0,void 0,function*(){if(typeof teamId==='undefined'){throw new AppwriteException('Missing required parameter: "teamId"');}
if(typeof email==='undefined'){throw new AppwriteException('Missing required parameter: "email"');}
@ -462,9 +473,10 @@ if(typeof userId==='undefined'){throw new AppwriteException('Missing required pa
if(typeof secret==='undefined'){throw new AppwriteException('Missing required parameter: "secret"');}
let path='/teams/{teamId}/memberships/{membershipId}/status'.replace('{teamId}',teamId).replace('{membershipId}',membershipId);let payload={};if(typeof userId!=='undefined'){payload['userId']=userId;}
if(typeof secret!=='undefined'){payload['secret']=secret;}
const uri=new URL(this.config.endpoint+path);return yield this.call('patch',uri,{'content-type':'application/json',},payload);})};this.users={list:(search,limit,offset,orderType)=>__awaiter(this,void 0,void 0,function*(){let path='/users';let payload={};if(typeof search!=='undefined'){payload['search']=search;}
const uri=new URL(this.config.endpoint+path);return yield this.call('patch',uri,{'content-type':'application/json',},payload);})};this.users={list:(search,limit,offset,after,orderType)=>__awaiter(this,void 0,void 0,function*(){let path='/users';let payload={};if(typeof search!=='undefined'){payload['search']=search;}
if(typeof limit!=='undefined'){payload['limit']=limit;}
if(typeof offset!=='undefined'){payload['offset']=offset;}
if(typeof after!=='undefined'){payload['after']=after;}
if(typeof orderType!=='undefined'){payload['orderType']=orderType;}
const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),create:(userId,email,password,name)=>__awaiter(this,void 0,void 0,function*(){if(typeof userId==='undefined'){throw new AppwriteException('Missing required parameter: "userId"');}
if(typeof email==='undefined'){throw new AppwriteException('Missing required parameter: "email"');}
@ -475,8 +487,17 @@ if(typeof password!=='undefined'){payload['password']=password;}
if(typeof name!=='undefined'){payload['name']=name;}
const uri=new URL(this.config.endpoint+path);return yield this.call('post',uri,{'content-type':'application/json',},payload);}),get:(userId)=>__awaiter(this,void 0,void 0,function*(){if(typeof userId==='undefined'){throw new AppwriteException('Missing required parameter: "userId"');}
let path='/users/{userId}'.replace('{userId}',userId);let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),delete:(userId)=>__awaiter(this,void 0,void 0,function*(){if(typeof userId==='undefined'){throw new AppwriteException('Missing required parameter: "userId"');}
let path='/users/{userId}'.replace('{userId}',userId);let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('delete',uri,{'content-type':'application/json',},payload);}),getLogs:(userId)=>__awaiter(this,void 0,void 0,function*(){if(typeof userId==='undefined'){throw new AppwriteException('Missing required parameter: "userId"');}
let path='/users/{userId}/logs'.replace('{userId}',userId);let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),getPrefs:(userId)=>__awaiter(this,void 0,void 0,function*(){if(typeof userId==='undefined'){throw new AppwriteException('Missing required parameter: "userId"');}
let path='/users/{userId}'.replace('{userId}',userId);let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('delete',uri,{'content-type':'application/json',},payload);}),updateEmail:(userId,email)=>__awaiter(this,void 0,void 0,function*(){if(typeof userId==='undefined'){throw new AppwriteException('Missing required parameter: "userId"');}
if(typeof email==='undefined'){throw new AppwriteException('Missing required parameter: "email"');}
let path='/users/{userId}/email'.replace('{userId}',userId);let payload={};if(typeof email!=='undefined'){payload['email']=email;}
const uri=new URL(this.config.endpoint+path);return yield this.call('patch',uri,{'content-type':'application/json',},payload);}),getLogs:(userId)=>__awaiter(this,void 0,void 0,function*(){if(typeof userId==='undefined'){throw new AppwriteException('Missing required parameter: "userId"');}
let path='/users/{userId}/logs'.replace('{userId}',userId);let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),updateName:(userId,name)=>__awaiter(this,void 0,void 0,function*(){if(typeof userId==='undefined'){throw new AppwriteException('Missing required parameter: "userId"');}
if(typeof name==='undefined'){throw new AppwriteException('Missing required parameter: "name"');}
let path='/users/{userId}/name'.replace('{userId}',userId);let payload={};if(typeof name!=='undefined'){payload['name']=name;}
const uri=new URL(this.config.endpoint+path);return yield this.call('patch',uri,{'content-type':'application/json',},payload);}),updatePassword:(userId,password)=>__awaiter(this,void 0,void 0,function*(){if(typeof userId==='undefined'){throw new AppwriteException('Missing required parameter: "userId"');}
if(typeof password==='undefined'){throw new AppwriteException('Missing required parameter: "password"');}
let path='/users/{userId}/password'.replace('{userId}',userId);let payload={};if(typeof password!=='undefined'){payload['password']=password;}
const uri=new URL(this.config.endpoint+path);return yield this.call('patch',uri,{'content-type':'application/json',},payload);}),getPrefs:(userId)=>__awaiter(this,void 0,void 0,function*(){if(typeof userId==='undefined'){throw new AppwriteException('Missing required parameter: "userId"');}
let path='/users/{userId}/prefs'.replace('{userId}',userId);let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),updatePrefs:(userId,prefs)=>__awaiter(this,void 0,void 0,function*(){if(typeof userId==='undefined'){throw new AppwriteException('Missing required parameter: "userId"');}
if(typeof prefs==='undefined'){throw new AppwriteException('Missing required parameter: "prefs"');}
let path='/users/{userId}/prefs'.replace('{userId}',userId);let payload={};if(typeof prefs!=='undefined'){payload['prefs']=prefs;}

View file

@ -111,7 +111,7 @@ result+'")": '+
error);}
if(debug){console.info("debug-ls-if result:",result);}
paths=expression.getPaths();let prv=element.$lsSkip;element.$lsSkip=!result;if(!result){element.style.visibility="hidden";element.style.display="none";}else{element.style.removeProperty("display");element.style.removeProperty("visibility");}
if(prv===true&&element.$lsSkip===false){view.render(element);}};check();for(let i=0;i<paths.length;i++){let path=paths[i].split(".");while(path.length){container.bind(element,path.join("."),check);path.pop();}}},});window.ls.container.get("view").add({selector:"data-ls-loop",template:false,nested:false,controller:function(element,view,container,filter,window,expression){let expr=expression.parse(element.getAttribute("data-ls-loop"));let as=element.getAttribute("data-ls-as");let filterName=element.getAttribute("data-ls-filter");let key=element.getAttribute("data-ls-key")||"$index";let prefix=element.getAttribute("data-ls-prefix")||null;let postfix=element.getAttribute("data-ls-postfix")||null;let limit=parseInt(expression.parse(element.getAttribute("data-ls-limit")||"")||-1);let debug=element.getAttribute("data-debug")||false;let echo=function(){let array=container.path(expr);let counter=0;array=!array?[]:array;if(filterName){array=filter.apply(filterName,array);console.log(array);}
if(prv===true&&element.$lsSkip===false){view.render(element);}};check();for(let i=0;i<paths.length;i++){let path=paths[i].split(".");while(path.length){container.bind(element,path.join("."),check);path.pop();}}},});window.ls.container.get("view").add({selector:"data-ls-loop",template:false,nested:false,controller:function(element,view,container,filter,window,expression){let expr=expression.parse(element.getAttribute("data-ls-loop"));let as=element.getAttribute("data-ls-as");let filterName=element.getAttribute("data-ls-filter");let key=element.getAttribute("data-ls-key")||"$index";let prefix=element.getAttribute("data-ls-prefix")||null;let postfix=element.getAttribute("data-ls-postfix")||null;let limit=parseInt(expression.parse(element.getAttribute("data-ls-limit")||"")||-1);let debug=element.getAttribute("data-debug")||false;let echo=function(){let array=container.path(expr);let counter=0;array=!array?[]:array;if(filterName){array=filter.apply(filterName,array);}
let watch=!!(array&&array.__proxy);while(element.hasChildNodes()){element.removeChild(element.lastChild);element.lastChild=null;}
if(array instanceof Array&&typeof array!=="object"){throw new Error("Reference value must be array or object. "+typeof array+" given");}
let children=[];element.$lsSkip=true;element.style.visibility=0===array.length&&element.style.visibility==""?"hidden":"visible";if(prefix){prefixElement=document.getElementById(prefix);let html=prefixElement.innerHTML;prefixElement=template.cloneNode(true);if(prefixElement){prefixElement.innerHTML=html;element.appendChild(prefixElement);view.render(prefixElement);}}
@ -296,7 +296,7 @@ return'';}).add("runtimeLogo",function($value,env){if(env&&env.RUNTIMES&&env.RUN
return'';}).add("runtimeVersion",function($value,env){if(env&&env.RUNTIMES&&env.RUNTIMES[$value]){return env.RUNTIMES[$value].version;}
return'';}).add("indexAttributes",function($value){let output='';for(let i=0;i<$value.attributes.length;i++){output+=$value.attributes[i]+' ('+$value.orders[i]+'), '}
return output.slice(0,-2);}).add("collectionAttributes",function($value){if(!Array.isArray($value)){return[];}
$value.unshift({$id:'$id'});return $value;}).add("documentAttribute",function($value,attribute){if($value[attribute.$id]){return $value[attribute.$id];}
$value.unshift({$id:'$id'});return $value;}).add("documentAttribute",function($value,attribute){if($value[attribute.key]){return $value[attribute.key];}
return null;});function abbreviate(number,maxPlaces,forcePlaces,forceLetter){number=Number(number);forceLetter=forceLetter||false;if(forceLetter!==false){return annotate(number,maxPlaces,forcePlaces,forceLetter);}
let abbr;if(number>=1e12){abbr="T";}else if(number>=1e9){abbr="B";}else if(number>=1e6){abbr="M";}else if(number>=1e3){abbr="K";}else{abbr="";}
return annotate(number,maxPlaces,forcePlaces,abbr);}
@ -374,8 +374,8 @@ element.setAttribute("data-id-type",idType);info.innerHTML="Appwrite will genera
button.className=idType=="custom"?"icon-shuffle copy":"icon-edit copy";}
const syncEditorWithID=function(event){if(element.value!=='unique()'||idType!='auto'){writer.value=element.value;}
if(idType=='auto'){element.value='unique()';}}
const keypress=function(e){const key=e.which||e.keyCode;const ZERO=48;const NINE=57;const SMALL_A=97;const SMALL_Z=122;const CAPITAL_A=65;const CAPITAL_Z=90;const UNDERSCORE=95;const isNotValidDigit=key<ZERO||key>NINE;const isNotValidSmallAlphabet=key<SMALL_A||key>SMALL_Z;const isNotValidCapitalAlphabet=key<CAPITAL_A||key>CAPITAL_Z;if(key==UNDERSCORE&&e.target.value.length==0){e.preventDefault();}
if(key!=UNDERSCORE&&isNotValidDigit&&isNotValidSmallAlphabet&&isNotValidCapitalAlphabet){e.preventDefault();}}
const keypress=function(e){const key=e.which||e.keyCode;const ZERO=48;const NINE=57;const SMALL_A=97;const SMALL_Z=122;const CAPITAL_A=65;const CAPITAL_Z=90;const UNDERSCORE=95;const HYPHEN=45;const PERIOD=46;const isNotValidDigit=key<ZERO||key>NINE;const isNotValidSmallAlphabet=key<SMALL_A||key>SMALL_Z;const isNotValidCapitalAlphabet=key<CAPITAL_A||key>CAPITAL_Z;const isNotValidFirstChar=(key===UNDERSCORE||key===HYPHEN||key===PERIOD);if(isNotValidFirstChar&&e.target.value.length==0){e.preventDefault();}
if(key!=UNDERSCORE&&key!=HYPHEN&&key!=PERIOD&&isNotValidDigit&&isNotValidSmallAlphabet&&isNotValidCapitalAlphabet){e.preventDefault();}}
syncEditorWithID();setIdType(idType);writer.addEventListener("change",function(event){element.value=writer.value;});writer.form.addEventListener('reset',function(event){const resetEvent=new Event('reset');element.dispatchEvent(resetEvent);});element.addEventListener('reset',function(event){idType=element.getAttribute('data-id-type');setIdType(idType);});writer.addEventListener('keypress',keypress);button.addEventListener("click",switchType);}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-document",controller:function(element,container,search){var formsDocument=(element.dataset["formsDocument"]||'');var searchButton=(element.dataset["search"]||0);let path=container.scope(searchButton);element.addEventListener('click',function(){search.selected=element.value;search.path=path;document.dispatchEvent(new CustomEvent(formsDocument,{bubbles:false,cancelable:true}));});}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-duplications",controller:function(element){let validate=function(element){let duplication=0;let form=element.form;for(let i=0;i<form.elements.length;i++){let field=form.elements[i];if(field.name===element.name&&field.value===element.value){duplication++;}}
if(duplication>1){element.setCustomValidity("Duplicated value");}
else{element.setCustomValidity("");}};element.addEventListener('change',function(event){validate(event.target)});element.addEventListener('focus',function(event){validate(event.target)});element.addEventListener('blur',function(event){validate(event.target)});}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-document-preview",controller:function(element,container,search){element.addEventListener('change',function(){console.log(element.value);});}});})(window);(function(window){window.ls.container.get("view").add({selector:"data-forms-filter",controller:function(document,container,expression,element,form,di){let name=element.dataset["formsFilter"]||"";let events=element.dataset["event"]||"";let serialize=function(obj,prefix){let str=[],p;for(p in obj){if(obj.hasOwnProperty(p)){let k=prefix?prefix+"["+p+"]":p,v=obj[p];if(v===""){continue;}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -906,11 +906,12 @@
* @param {string} search
* @param {number} limit
* @param {number} offset
* @param {string} after
* @param {string} orderType
* @throws {AppwriteException}
* @returns {Promise}
*/
listCollections: (search, limit, offset, orderType) => __awaiter(this, void 0, void 0, function* () {
listCollections: (search, limit, offset, after, orderType) => __awaiter(this, void 0, void 0, function* () {
let path = '/database/collections';
let payload = {};
if (typeof search !== 'undefined') {
@ -922,6 +923,9 @@
if (typeof offset !== 'undefined') {
payload['offset'] = offset;
}
if (typeof after !== 'undefined') {
payload['after'] = after;
}
if (typeof orderType !== 'undefined') {
payload['orderType'] = orderType;
}
@ -937,18 +941,22 @@
*
* @param {string} collectionId
* @param {string} name
* @param {string} permission
* @param {string} read
* @param {string} write
* @throws {AppwriteException}
* @returns {Promise}
*/
createCollection: (collectionId, name, read, write) => __awaiter(this, void 0, void 0, function* () {
createCollection: (collectionId, name, permission, read, write) => __awaiter(this, void 0, void 0, function* () {
if (typeof collectionId === 'undefined') {
throw new AppwriteException('Missing required parameter: "collectionId"');
}
if (typeof name === 'undefined') {
throw new AppwriteException('Missing required parameter: "name"');
}
if (typeof permission === 'undefined') {
throw new AppwriteException('Missing required parameter: "permission"');
}
if (typeof read === 'undefined') {
throw new AppwriteException('Missing required parameter: "read"');
}
@ -963,6 +971,9 @@
if (typeof name !== 'undefined') {
payload['name'] = name;
}
if (typeof permission !== 'undefined') {
payload['permission'] = permission;
}
if (typeof read !== 'undefined') {
payload['read'] = read;
}
@ -1002,23 +1013,30 @@
*
* @param {string} collectionId
* @param {string} name
* @param {string} permission
* @param {string} read
* @param {string} write
* @throws {AppwriteException}
* @returns {Promise}
*/
updateCollection: (collectionId, name, read, write) => __awaiter(this, void 0, void 0, function* () {
updateCollection: (collectionId, name, permission, read, write) => __awaiter(this, void 0, void 0, function* () {
if (typeof collectionId === 'undefined') {
throw new AppwriteException('Missing required parameter: "collectionId"');
}
if (typeof name === 'undefined') {
throw new AppwriteException('Missing required parameter: "name"');
}
if (typeof permission === 'undefined') {
throw new AppwriteException('Missing required parameter: "permission"');
}
let path = '/database/collections/{collectionId}'.replace('{collectionId}', collectionId);
let payload = {};
if (typeof name !== 'undefined') {
payload['name'] = name;
}
if (typeof permission !== 'undefined') {
payload['permission'] = permission;
}
if (typeof read !== 'undefined') {
payload['read'] = read;
}
@ -1345,23 +1363,19 @@
*
* @param {string} collectionId
* @param {string} attributeId
* @param {number} size
* @param {boolean} required
* @param {string} xdefault
* @param {boolean} array
* @throws {AppwriteException}
* @returns {Promise}
*/
createUrlAttribute: (collectionId, attributeId, size, required, xdefault, array) => __awaiter(this, void 0, void 0, function* () {
createUrlAttribute: (collectionId, attributeId, required, xdefault, array) => __awaiter(this, void 0, void 0, function* () {
if (typeof collectionId === 'undefined') {
throw new AppwriteException('Missing required parameter: "collectionId"');
}
if (typeof attributeId === 'undefined') {
throw new AppwriteException('Missing required parameter: "attributeId"');
}
if (typeof size === 'undefined') {
throw new AppwriteException('Missing required parameter: "size"');
}
if (typeof required === 'undefined') {
throw new AppwriteException('Missing required parameter: "required"');
}
@ -1370,9 +1384,6 @@
if (typeof attributeId !== 'undefined') {
payload['attributeId'] = attributeId;
}
if (typeof size !== 'undefined') {
payload['size'] = size;
}
if (typeof required !== 'undefined') {
payload['required'] = required;
}
@ -1445,12 +1456,13 @@
* @param {string[]} queries
* @param {number} limit
* @param {number} offset
* @param {string} after
* @param {string[]} orderAttributes
* @param {string[]} orderTypes
* @throws {AppwriteException}
* @returns {Promise}
*/
listDocuments: (collectionId, queries, limit, offset, orderAttributes, orderTypes) => __awaiter(this, void 0, void 0, function* () {
listDocuments: (collectionId, queries, limit, offset, after, orderAttributes, orderTypes) => __awaiter(this, void 0, void 0, function* () {
if (typeof collectionId === 'undefined') {
throw new AppwriteException('Missing required parameter: "collectionId"');
}
@ -1465,6 +1477,9 @@
if (typeof offset !== 'undefined') {
payload['offset'] = offset;
}
if (typeof after !== 'undefined') {
payload['after'] = after;
}
if (typeof orderAttributes !== 'undefined') {
payload['orderAttributes'] = orderAttributes;
}
@ -1670,8 +1685,6 @@
if (typeof orders !== 'undefined') {
payload['orders'] = orders;
}
console.log(collectionId, indexId, type, attributes, orders);
const uri = new URL(this.config.endpoint + path);
return yield this.call('post', uri, {
'content-type': 'application/json',
@ -1724,7 +1737,7 @@
}, payload);
}),
/**
* Get Collection Logs
* List Collection Logs
*
* Get the collection activity logs list by its unique ID.
*
@ -1754,11 +1767,12 @@
* @param {string} search
* @param {number} limit
* @param {number} offset
* @param {string} after
* @param {string} orderType
* @throws {AppwriteException}
* @returns {Promise}
*/
list: (search, limit, offset, orderType) => __awaiter(this, void 0, void 0, function* () {
list: (search, limit, offset, after, orderType) => __awaiter(this, void 0, void 0, function* () {
let path = '/functions';
let payload = {};
if (typeof search !== 'undefined') {
@ -1770,6 +1784,9 @@
if (typeof offset !== 'undefined') {
payload['offset'] = offset;
}
if (typeof after !== 'undefined') {
payload['after'] = after;
}
if (typeof orderType !== 'undefined') {
payload['orderType'] = orderType;
}
@ -1941,10 +1958,11 @@
* @param {string} functionId
* @param {number} limit
* @param {number} offset
* @param {string} after
* @throws {AppwriteException}
* @returns {Promise}
*/
listExecutions: (functionId, limit, offset) => __awaiter(this, void 0, void 0, function* () {
listExecutions: (functionId, limit, offset, after) => __awaiter(this, void 0, void 0, function* () {
if (typeof functionId === 'undefined') {
throw new AppwriteException('Missing required parameter: "functionId"');
}
@ -1956,6 +1974,9 @@
if (typeof offset !== 'undefined') {
payload['offset'] = offset;
}
if (typeof after !== 'undefined') {
payload['after'] = after;
}
const uri = new URL(this.config.endpoint + path);
return yield this.call('get', uri, {
'content-type': 'application/json',
@ -2051,11 +2072,12 @@
* @param {string} search
* @param {number} limit
* @param {number} offset
* @param {string} after
* @param {string} orderType
* @throws {AppwriteException}
* @returns {Promise}
*/
listTags: (functionId, search, limit, offset, orderType) => __awaiter(this, void 0, void 0, function* () {
listTags: (functionId, search, limit, offset, after, orderType) => __awaiter(this, void 0, void 0, function* () {
if (typeof functionId === 'undefined') {
throw new AppwriteException('Missing required parameter: "functionId"');
}
@ -2070,6 +2092,9 @@
if (typeof offset !== 'undefined') {
payload['offset'] = offset;
}
if (typeof after !== 'undefined') {
payload['after'] = after;
}
if (typeof orderType !== 'undefined') {
payload['orderType'] = orderType;
}
@ -2516,11 +2541,12 @@
* @param {string} search
* @param {number} limit
* @param {number} offset
* @param {string} after
* @param {string} orderType
* @throws {AppwriteException}
* @returns {Promise}
*/
list: (search, limit, offset, orderType) => __awaiter(this, void 0, void 0, function* () {
list: (search, limit, offset, after, orderType) => __awaiter(this, void 0, void 0, function* () {
let path = '/projects';
let payload = {};
if (typeof search !== 'undefined') {
@ -2532,6 +2558,9 @@
if (typeof offset !== 'undefined') {
payload['offset'] = offset;
}
if (typeof after !== 'undefined') {
payload['after'] = after;
}
if (typeof orderType !== 'undefined') {
payload['orderType'] = orderType;
}
@ -3457,11 +3486,12 @@
* @param {string} search
* @param {number} limit
* @param {number} offset
* @param {string} after
* @param {string} orderType
* @throws {AppwriteException}
* @returns {Promise}
*/
listFiles: (search, limit, offset, orderType) => __awaiter(this, void 0, void 0, function* () {
listFiles: (search, limit, offset, after, orderType) => __awaiter(this, void 0, void 0, function* () {
let path = '/storage/files';
let payload = {};
if (typeof search !== 'undefined') {
@ -3473,6 +3503,9 @@
if (typeof offset !== 'undefined') {
payload['offset'] = offset;
}
if (typeof after !== 'undefined') {
payload['after'] = after;
}
if (typeof orderType !== 'undefined') {
payload['orderType'] = orderType;
}
@ -3728,11 +3761,12 @@
* @param {string} search
* @param {number} limit
* @param {number} offset
* @param {string} after
* @param {string} orderType
* @throws {AppwriteException}
* @returns {Promise}
*/
list: (search, limit, offset, orderType) => __awaiter(this, void 0, void 0, function* () {
list: (search, limit, offset, after, orderType) => __awaiter(this, void 0, void 0, function* () {
let path = '/teams';
let payload = {};
if (typeof search !== 'undefined') {
@ -3744,6 +3778,9 @@
if (typeof offset !== 'undefined') {
payload['offset'] = offset;
}
if (typeof after !== 'undefined') {
payload['after'] = after;
}
if (typeof orderType !== 'undefined') {
payload['orderType'] = orderType;
}
@ -3869,11 +3906,12 @@
* @param {string} search
* @param {number} limit
* @param {number} offset
* @param {string} after
* @param {string} orderType
* @throws {AppwriteException}
* @returns {Promise}
*/
getMemberships: (teamId, search, limit, offset, orderType) => __awaiter(this, void 0, void 0, function* () {
getMemberships: (teamId, search, limit, offset, after, orderType) => __awaiter(this, void 0, void 0, function* () {
if (typeof teamId === 'undefined') {
throw new AppwriteException('Missing required parameter: "teamId"');
}
@ -3888,6 +3926,9 @@
if (typeof offset !== 'undefined') {
payload['offset'] = offset;
}
if (typeof after !== 'undefined') {
payload['after'] = after;
}
if (typeof orderType !== 'undefined') {
payload['orderType'] = orderType;
}
@ -4088,11 +4129,12 @@
* @param {string} search
* @param {number} limit
* @param {number} offset
* @param {string} after
* @param {string} orderType
* @throws {AppwriteException}
* @returns {Promise}
*/
list: (search, limit, offset, orderType) => __awaiter(this, void 0, void 0, function* () {
list: (search, limit, offset, after, orderType) => __awaiter(this, void 0, void 0, function* () {
let path = '/users';
let payload = {};
if (typeof search !== 'undefined') {
@ -4104,6 +4146,9 @@
if (typeof offset !== 'undefined') {
payload['offset'] = offset;
}
if (typeof after !== 'undefined') {
payload['after'] = after;
}
if (typeof orderType !== 'undefined') {
payload['orderType'] = orderType;
}
@ -4193,6 +4238,33 @@
'content-type': 'application/json',
}, payload);
}),
/**
* Update Email
*
* Update the user email by its unique ID.
*
* @param {string} userId
* @param {string} email
* @throws {AppwriteException}
* @returns {Promise}
*/
updateEmail: (userId, email) => __awaiter(this, void 0, void 0, function* () {
if (typeof userId === 'undefined') {
throw new AppwriteException('Missing required parameter: "userId"');
}
if (typeof email === 'undefined') {
throw new AppwriteException('Missing required parameter: "email"');
}
let path = '/users/{userId}/email'.replace('{userId}', userId);
let payload = {};
if (typeof email !== 'undefined') {
payload['email'] = email;
}
const uri = new URL(this.config.endpoint + path);
return yield this.call('patch', uri, {
'content-type': 'application/json',
}, payload);
}),
/**
* Get User Logs
*
@ -4213,6 +4285,60 @@
'content-type': 'application/json',
}, payload);
}),
/**
* Update Name
*
* Update the user name by its unique ID.
*
* @param {string} userId
* @param {string} name
* @throws {AppwriteException}
* @returns {Promise}
*/
updateName: (userId, name) => __awaiter(this, void 0, void 0, function* () {
if (typeof userId === 'undefined') {
throw new AppwriteException('Missing required parameter: "userId"');
}
if (typeof name === 'undefined') {
throw new AppwriteException('Missing required parameter: "name"');
}
let path = '/users/{userId}/name'.replace('{userId}', userId);
let payload = {};
if (typeof name !== 'undefined') {
payload['name'] = name;
}
const uri = new URL(this.config.endpoint + path);
return yield this.call('patch', uri, {
'content-type': 'application/json',
}, payload);
}),
/**
* Update Password
*
* Update the user password by its unique ID.
*
* @param {string} userId
* @param {string} password
* @throws {AppwriteException}
* @returns {Promise}
*/
updatePassword: (userId, password) => __awaiter(this, void 0, void 0, function* () {
if (typeof userId === 'undefined') {
throw new AppwriteException('Missing required parameter: "userId"');
}
if (typeof password === 'undefined') {
throw new AppwriteException('Missing required parameter: "password"');
}
let path = '/users/{userId}/password'.replace('{userId}', userId);
let payload = {};
if (typeof password !== 'undefined') {
payload['password'] = password;
}
const uri = new URL(this.config.endpoint + path);
return yield this.call('patch', uri, {
'content-type': 'application/json',
}, payload);
}),
/**
* Get User Preferences
*
@ -4544,4 +4670,4 @@
Object.defineProperty(exports, '__esModule', { value: true });
}(this.window = this.window || {}, null, window));
}(this.window = this.window || {}, null, window));

View file

@ -275,8 +275,8 @@ window.ls.filter
return $value;
})
.add("documentAttribute", function($value, attribute) {
if($value[attribute.$id]) {
return $value[attribute.$id];
if($value[attribute.key]) {
return $value[attribute.key];
}
return null;

View file

@ -114,16 +114,19 @@
const CAPITAL_A = 65;
const CAPITAL_Z = 90;
const UNDERSCORE = 95;
const HYPHEN = 45;
const PERIOD = 46;
const isNotValidDigit = key < ZERO || key > NINE;
const isNotValidSmallAlphabet = key < SMALL_A || key > SMALL_Z;
const isNotValidCapitalAlphabet = key < CAPITAL_A || key > CAPITAL_Z;
const isNotValidFirstChar = (key === UNDERSCORE || key === HYPHEN || key === PERIOD);
//Leading underscore is prevented
if (key == UNDERSCORE && e.target.value.length == 0) {
if ( isNotValidFirstChar && e.target.value.length == 0) {
e.preventDefault();
}
if (key != UNDERSCORE && isNotValidDigit && isNotValidSmallAlphabet && isNotValidCapitalAlphabet) {
if (key != UNDERSCORE && key != HYPHEN && key != PERIOD && isNotValidDigit && isNotValidSmallAlphabet && isNotValidCapitalAlphabet) {
e.preventDefault();
}
}

View file

@ -20,13 +20,17 @@
z-index: 1;
opacity: 1!important;
&.hide {
display: none;
}
&:before {
content: "";
position: absolute;
// content: "";
// position: absolute;
width: 100%;
height: 100%;
z-index: 0;
background: var(--config-color-background-focus);
// background: var(--config-color-background-focus);
}
&.inline {

View file

@ -783,6 +783,12 @@
"css": "key",
"code": 59479,
"src": "elusive"
},
{
"uid": "3256ef03b16e7ab51235bc7378b53bb5",
"css": "boolean",
"code": 61957,
"src": "fontawesome"
}
]
}

View file

@ -1209,6 +1209,12 @@ ol {
.pull-start;
}
i.avatar {
text-align: center;
background: var(--config-color-dark);
color: var(--config-color-background-fade);
}
&:last-child {
border-bottom: none;
}

File diff suppressed because one or more lines are too long

View file

@ -835,11 +835,7 @@
}
}
.scroll-to {
display: none;
}
@media @desktops {
.logo {
.top {

View file

@ -42,6 +42,7 @@ abstract class Migration
'0.9.1' => 'V08',
'0.9.2' => 'V08',
'0.9.3' => 'V08',
'0.9.4' => 'V08',
'0.10.0' => 'V08', // TODO Eldad: `I need this to pass the tests`
];

View file

@ -4,7 +4,6 @@ namespace Appwrite\Specification\Format;
use Appwrite\Specification\Format;
use Appwrite\Template\Template;
use stdClass;
use Utopia\Validator;
class OpenAPI3 extends Format
@ -21,6 +20,34 @@ class OpenAPI3 extends Format
return 'Open API 3';
}
/**
* Get Used Models
*
* Recursively get all used models
*
* @param object $model
* @param array $models
*
* @return void
*/
protected function getUsedModels($model, array &$usedModels)
{
if (is_string($model) && !in_array($model, ['string', 'integer', 'boolean', 'json', 'float', 'double'])) {
$usedModels[] = $model;
return;
}
if (!is_object($model)) return;
foreach ($model->getRules() as $rule) {
if(\is_array($rule['type'])) {
foreach ($rule['type'] as $type) {
$this->getUsedModels($type, $usedModels);
}
} else {
$this->getUsedModels($rule['type'], $usedModels);
}
}
}
/**
* Parse
*
@ -71,7 +98,7 @@ class OpenAPI3 extends Format
if (isset($output['components']['securitySchemes']['Project'])) {
$output['components']['securitySchemes']['Project']['x-appwrite'] = ['demo' => '5df5acd0d48c2'];
}
if (isset($output['components']['securitySchemes']['Key'])) {
$output['components']['securitySchemes']['Key']['x-appwrite'] = ['demo' => '919c2d18fb5d4...a2ae413da83346ad2'];
}
@ -79,7 +106,7 @@ class OpenAPI3 extends Format
if (isset($output['securityDefinitions']['JWT'])) {
$output['securityDefinitions']['JWT']['x-appwrite'] = ['demo' => 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ...'];
}
if (isset($output['components']['securitySchemes']['Locale'])) {
$output['components']['securitySchemes']['Locale']['x-appwrite'] = ['demo' => 'en'];
}
@ -91,7 +118,7 @@ class OpenAPI3 extends Format
$usedModels = [];
foreach ($this->routes as $route) { /** @var \Utopia\Route $route */
$url = \str_replace('/v1', '', $route->getURL());
$url = \str_replace('/v1', '', $route->getPath());
$scope = $route->getLabel('scope', '');
$hide = $route->getLabel('sdk.hide', false);
$consumes = [$route->getLabel('sdk.request.type', 'application/json')];
@ -103,7 +130,7 @@ class OpenAPI3 extends Format
$id = $route->getLabel('sdk.method', \uniqid());
$desc = (!empty($route->getLabel('sdk.description', ''))) ? \realpath(__DIR__.'/../../../../'.$route->getLabel('sdk.description', '')) : null;
$produces = $route->getLabel('sdk.response.type', null);
$model = $route->getLabel('sdk.response.model', 'none');
$model = $route->getLabel('sdk.response.model', 'none');
$routeSecurity = $route->getLabel('sdk.auth', []);
$sdkPlatofrms = [];
@ -127,7 +154,7 @@ class OpenAPI3 extends Format
if(empty($routeSecurity)) {
$sdkPlatofrms[] = APP_PLATFORM_CLIENT;
}
$temp = [
'summary' => $route->getDesc(),
'operationId' => $route->getLabel('sdk.namespace', 'default').ucfirst($id),
@ -153,13 +180,24 @@ class OpenAPI3 extends Format
];
foreach ($this->models as $key => $value) {
if($value->getType() === $model) {
$model = $value;
break;
if(\is_array($model)) {
$model = \array_map(function($m) use($value) {
if($m === $value->getType()) {
return $value;
}
return $m;
}, $model);
} else {
if($value->getType() === $model) {
$model = $value;
break;
}
}
}
if($model->isNone()) {
if(!(\is_array($model)) && $model->isNone()) {
$temp['responses'][(string)$route->getLabel('sdk.response.code', '500')] = [
'description' => (in_array($produces, [
'image/*',
@ -176,17 +214,43 @@ class OpenAPI3 extends Format
// ],
];
} else {
$usedModels[] = $model->getType();
$temp['responses'][(string)$route->getLabel('sdk.response.code', '500')] = [
'description' => $model->getName(),
'content' => [
$produces => [
'schema' => [
'$ref' => '#/components/schemas/'.$model->getType(),
if(\is_array($model)) {
$modelDescription = \join(', or ', \array_map(function ($m) {
return $m->getName();
}, $model));
// model has multiple possible responses, we will use oneOf
foreach ($model as $m) {
$usedModels[] = $m->getType();
}
$temp['responses'][(string)$route->getLabel('sdk.response.code', '500')] = [
'description' => $modelDescription,
'content' => [
$produces => [
'schema' => [
'oneOf' => \array_map(function($m) {
return ['$ref' => '#/components/schemas/'.$m->getType()];
}, $model)
],
],
],
],
];
];
} else {
// Response definition using one type
$usedModels[] = $model->getType();
$temp['responses'][(string)$route->getLabel('sdk.response.code', '500')] = [
'description' => $model->getName(),
'content' => [
$produces => [
'schema' => [
'$ref' => '#/components/schemas/'.$model->getType(),
],
],
],
];
}
}
if($route->getLabel('sdk.response.code', 500) === 204) {
@ -196,7 +260,7 @@ class OpenAPI3 extends Format
if ((!empty($scope))) { // && 'public' != $scope
$securities = ['Project' => []];
foreach($route->getLabel('sdk.auth', []) as $security) {
if(array_key_exists($security, $this->keys)) {
$securities[$security] = [];
@ -352,11 +416,7 @@ class OpenAPI3 extends Format
$output['paths'][$url][\strtolower($route->getMethod())] = $temp;
}
foreach ($this->models as $model) {
foreach ($model->getRules() as $rule) {
if (!in_array($rule['type'], ['string', 'integer', 'boolean', 'json', 'float'])) {
$usedModels[] = $rule['type'];
}
}
$this->getUsedModels($model, $usedModels);
}
foreach ($this->models as $model) {
if (!in_array($model->getType(), $usedModels) && $model->getType() !== 'error') {
@ -378,7 +438,7 @@ class OpenAPI3 extends Format
if($model->isAny()) {
$output['components']['schemas'][$model->getType()]['additionalProperties'] = true;
}
if(!empty($required)) {
$output['components']['schemas'][$model->getType()]['required'] = $required;
}
@ -393,7 +453,7 @@ class OpenAPI3 extends Format
case 'json':
$type = 'string';
break;
case 'integer':
$type = 'integer';
$format = 'int32';
@ -403,18 +463,39 @@ class OpenAPI3 extends Format
$type = 'number';
$format = 'float';
break;
case 'double':
$type = 'number';
$format = 'double';
break;
case 'boolean':
$type = 'boolean';
break;
default:
$type = 'object';
$rule['type'] = ($rule['type']) ? $rule['type'] : 'none';
$items = [
'$ref' => '#/components/schemas/'.$rule['type'],
];
if(\is_array($rule['type'])) {
if($rule['array']) {
$items = [
'anyOf' => \array_map(function($type) {
return ['$ref' => '#/components/schemas/'.$type];
}, $rule['type'])
];
} else {
$items = [
'oneOf' => \array_map(function($type) {
return ['$ref' => '#/components/schemas/'.$type];
}, $rule['type'])
];
}
} else {
$items = [
'$ref' => '#/components/schemas/'.$rule['type'],
];
}
break;
}

View file

@ -21,6 +21,34 @@ class Swagger2 extends Format
return 'Swagger 2';
}
/**
* Get Used Models
*
* Recursively get all used models
*
* @param object $model
* @param array $models
*
* @return void
*/
protected function getUsedModels($model, array &$usedModels)
{
if (is_string($model) && !in_array($model, ['string', 'integer', 'boolean', 'json', 'float', 'double'])) {
$usedModels[] = $model;
return;
}
if (!is_object($model)) return;
foreach ($model->getRules() as $rule) {
if(\is_array($rule['type'])) {
foreach ($rule['type'] as $type) {
$this->getUsedModels($type, $usedModels);
}
} else {
$this->getUsedModels($rule['type'], $usedModels);
}
}
}
/**
* Parse
*
@ -69,15 +97,15 @@ class Swagger2 extends Format
if (isset($output['securityDefinitions']['Project'])) {
$output['securityDefinitions']['Project']['x-appwrite'] = ['demo' => '5df5acd0d48c2'];
}
if (isset($output['securityDefinitions']['Key'])) {
$output['securityDefinitions']['Key']['x-appwrite'] = ['demo' => '919c2d18fb5d4...a2ae413da83346ad2'];
}
if (isset($output['securityDefinitions']['JWT'])) {
$output['securityDefinitions']['JWT']['x-appwrite'] = ['demo' => 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ...'];
}
if (isset($output['securityDefinitions']['Locale'])) {
$output['securityDefinitions']['Locale']['x-appwrite'] = ['demo' => 'en'];
}
@ -89,7 +117,7 @@ class Swagger2 extends Format
$usedModels = [];
foreach ($this->routes as $route) { /** @var \Utopia\Route $route */
$url = \str_replace('/v1', '', $route->getURL());
$url = \str_replace('/v1', '', $route->getPath());
$scope = $route->getLabel('scope', '');
$hide = $route->getLabel('sdk.hide', false);
$consumes = [$route->getLabel('sdk.request.type', 'application/json')];
@ -125,7 +153,7 @@ class Swagger2 extends Format
if(empty($routeSecurity)) {
$sdkPlatofrms[] = APP_PLATFORM_CLIENT;
}
$temp = [
'summary' => $route->getDesc(),
'operationId' => $route->getLabel('sdk.namespace', 'default').ucfirst($id),
@ -155,13 +183,22 @@ class Swagger2 extends Format
}
foreach ($this->models as $key => $value) {
if($value->getType() === $model) {
$model = $value;
break;
if(\is_array($model)) {
$model = \array_map(function($m) use($value) {
if($m === $value->getType()) {
return $value;
}
return $m;
}, $model);
} else {
if($value->getType() === $model) {
$model = $value;
break;
}
}
}
if($model->isNone()) {
if(!(\is_array($model)) && $model->isNone()) {
$temp['responses'][(string)$route->getLabel('sdk.response.code', '500')] = [
'description' => (in_array($produces, [
'image/*',
@ -178,13 +215,41 @@ class Swagger2 extends Format
],
];
} else {
$usedModels[] = $model->getType();
$temp['responses'][(string)$route->getLabel('sdk.response.code', '500')] = [
'description' => $model->getName(),
'schema' => [
'$ref' => '#/definitions/'.$model->getType(),
],
];
if(\is_array($model)) {
$modelDescription = \join(', or ', \array_map(function ($m) {
return $m->getName();
}, $model));
// model has multiple possible responses, we will use oneOf
foreach ($model as $m) {
$usedModels[] = $m->getType();
}
$temp['responses'][(string)$route->getLabel('sdk.response.code', '500')] = [
'description' => $modelDescription,
'content' => [
$produces => [
'schema' => [
'oneOf' => \array_map(function($m) {
return ['$ref' => '#/definitions/'.$m->getType()];
}, $model)
],
],
],
];
} else {
// Response definition using one type
$usedModels[] = $model->getType();
$temp['responses'][(string)$route->getLabel('sdk.response.code', '500')] = [
'description' => $model->getName(),
'content' => [
$produces => [
'schema' => [
'$ref' => '#/definitions/'.$model->getType(),
],
],
],
];
}
}
if(in_array($route->getLabel('sdk.response.code', 500), [204, 301, 302, 308], true)) {
@ -194,7 +259,7 @@ class Swagger2 extends Format
if ((!empty($scope))) { // && 'public' != $scope
$securities = ['Project' => []];
foreach($route->getLabel('sdk.auth', []) as $security) {
if(array_key_exists($security, $this->keys)) {
$securities[$security] = [];
@ -204,7 +269,7 @@ class Swagger2 extends Format
$temp['x-appwrite']['auth'] = array_slice($securities, 0, $this->authCount);
$temp['security'][] = $securities;
}
$body = [
'name' => 'payload',
'in' => 'body',
@ -354,15 +419,9 @@ class Swagger2 extends Format
$output['paths'][$url][\strtolower($route->getMethod())] = $temp;
}
foreach ($this->models as $model) {
foreach ($model->getRules() as $rule) {
if (
in_array($model->getType(), $usedModels)
&& !in_array($rule['type'], ['string', 'integer', 'boolean', 'json', 'float'])
) {
$usedModels[] = $rule['type'];
}
}
$this->getUsedModels($model, $usedModels);
}
foreach ($this->models as $model) {
if (!in_array($model->getType(), $usedModels)) {
continue;
@ -383,7 +442,7 @@ class Swagger2 extends Format
if($model->isAny()) {
$output['definitions'][$model->getType()]['additionalProperties'] = true;
}
if(!empty($required)) {
$output['definitions'][$model->getType()]['required'] = $required;
}
@ -398,7 +457,7 @@ class Swagger2 extends Format
case 'json':
$type = 'string';
break;
case 'integer':
$type = 'integer';
$format = 'int32';
@ -408,19 +467,40 @@ class Swagger2 extends Format
$type = 'number';
$format = 'float';
break;
case 'double':
$type = 'number';
$format = 'double';
break;
case 'boolean':
$type = 'boolean';
break;
default:
$type = 'object';
$rule['type'] = ($rule['type']) ? $rule['type'] : 'none';
$items = [
'type' => $type,
'$ref' => '#/definitions/'.$rule['type'],
];
if(\is_array($rule['type'])) {
if($rule['array']) {
$items = [
'anyOf' => \array_map(function($type) {
return ['$ref' => '#/definitions/'.$type];
}, $rule['type'])
];
} else {
$items = [
'oneOf' => \array_map(function($type) {
return ['$ref' => '#/definitions/'.$type];
}, $rule['type'])
];
}
} else {
$items = [
'type' => $type,
'$ref' => '#/definitions/'.$rule['type'],
];
}
break;
}

View file

@ -94,7 +94,7 @@ class Stats
$functionExecutionTime = $this->params['functionExecutionTime'] ?? 0;
$functionStatus = $this->params['functionStatus'] ?? '';
$tags = ",project={$projectId},version=" . App::getEnv('_APP_VERSION', 'UNKNOWN');
$tags = ",projectId={$projectId},version=" . App::getEnv('_APP_VERSION', 'UNKNOWN');
// the global namespace is prepended to every key (optional)
$this->statsd->setNamespace($this->namespace);
@ -112,7 +112,70 @@ class Stats
$this->statsd->count('network.outbound' . $tags, $networkResponseSize);
$this->statsd->count('network.all' . $tags, $networkRequestSize + $networkResponseSize);
$dbMetrics = [
'database.collections.create',
'database.collections.read',
'database.collections.update',
'database.collections.delete',
'database.documents.create',
'database.documents.read',
'database.documents.update',
'database.documents.delete',
];
foreach ($dbMetrics as $metric) {
$value = $this->params[$metric] ?? 0;
if ($value >= 1) {
$tags = ",projectId={$projectId},collectionId=" . ($this->params['collectionId'] ?? '');
$this->statsd->increment($metric . $tags);
}
}
$storageMertics = [
'storage.files.create',
'storage.files.read',
'storage.files.update',
'storage.files.delete',
];
foreach ($storageMertics as $metric) {
$value = $this->params[$metric] ?? 0;
if ($value >= 1) {
$tags = ",projectId={$projectId},bucketId=" . ($this->params['bucketId'] ?? '');
$this->statsd->increment($metric . $tags);
}
}
$usersMetrics = [
'users.create',
'users.read',
'users.update',
'users.delete',
];
foreach ($usersMetrics as $metric) {
$value = $this->params[$metric] ?? 0;
if ($value >= 1) {
$tags = ",projectId={$projectId}";
$this->statsd->increment($metric . $tags);
}
}
$sessionsMetrics = [
'users.sessions.create',
'users.sessions.delete',
];
foreach ($sessionsMetrics as $metric) {
$value = $this->params[$metric] ?? 0;
if ($value >= 1) {
$tags = ",projectId={$projectId},provider=". ($this->params['provider'] ?? '');
$this->statsd->count($metric . $tags, $value);
}
}
if ($storage >= 1) {
$tags = ",projectId={$projectId},bucketId=" . ($this->params['bucketId'] ?? '');
$this->statsd->count('storage.all' . $tags, $storage);
}

View file

@ -11,6 +11,14 @@ use Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response\Model\None;
use Appwrite\Utopia\Response\Model\Any;
use Appwrite\Utopia\Response\Model\Attribute;
use Appwrite\Utopia\Response\Model\AttributeList;
use Appwrite\Utopia\Response\Model\AttributeString;
use Appwrite\Utopia\Response\Model\AttributeInteger;
use Appwrite\Utopia\Response\Model\AttributeFloat;
use Appwrite\Utopia\Response\Model\AttributeBoolean;
use Appwrite\Utopia\Response\Model\AttributeEmail;
use Appwrite\Utopia\Response\Model\AttributeIP;
use Appwrite\Utopia\Response\Model\AttributeURL;
use Appwrite\Utopia\Response\Model\BaseList;
use Appwrite\Utopia\Response\Model\Collection;
use Appwrite\Utopia\Response\Model\Continent;
@ -33,6 +41,7 @@ use Appwrite\Utopia\Response\Model\Team;
use Appwrite\Utopia\Response\Model\Locale;
use Appwrite\Utopia\Response\Model\Log;
use Appwrite\Utopia\Response\Model\Membership;
use Appwrite\Utopia\Response\Model\Metric;
use Appwrite\Utopia\Response\Model\Permissions;
use Appwrite\Utopia\Response\Model\Phone;
use Appwrite\Utopia\Response\Model\Platform;
@ -43,6 +52,13 @@ use Appwrite\Utopia\Response\Model\Token;
use Appwrite\Utopia\Response\Model\Webhook;
use Appwrite\Utopia\Response\Model\Preferences;
use Appwrite\Utopia\Response\Model\Mock; // Keep last
use Appwrite\Utopia\Response\Model\UsageBuckets;
use Appwrite\Utopia\Response\Model\UsageCollection;
use Appwrite\Utopia\Response\Model\UsageDatabase;
use Appwrite\Utopia\Response\Model\UsageFunctions;
use Appwrite\Utopia\Response\Model\UsageProject;
use Appwrite\Utopia\Response\Model\UsageStorage;
use Appwrite\Utopia\Response\Model\UsageUsers;
use stdClass;
/**
@ -56,19 +72,37 @@ class Response extends SwooleResponse
const MODEL_LOG = 'log';
const MODEL_LOG_LIST = 'logList';
const MODEL_ERROR = 'error';
const MODEL_METRIC = 'metric';
const MODEL_METRIC_LIST = 'metricList';
const MODEL_ERROR_DEV = 'errorDev';
const MODEL_BASE_LIST = 'baseList';
const MODEL_USAGE_DATABASE = 'usageDatabase';
const MODEL_USAGE_COLLECTION = 'usageCollection';
const MODEL_USAGE_USERS = 'usageUsers';
const MODEL_USAGE_BUCKETS = 'usageBuckets';
const MODEL_USAGE_STORAGE = 'usageStorage';
const MODEL_USAGE_FUNCTIONS = 'usageFunctions';
const MODEL_USAGE_PROJECT = 'usageProject';
// Database
const MODEL_COLLECTION = 'collection';
const MODEL_COLLECTION_LIST = 'collectionList';
const MODEL_ATTRIBUTE = 'attribute';
const MODEL_ATTRIBUTE_LIST = 'attributeList';
const MODEL_INDEX = 'index';
const MODEL_INDEX_LIST = 'indexList';
const MODEL_DOCUMENT = 'document';
const MODEL_DOCUMENT_LIST = 'documentList';
// Database Attributes
const MODEL_ATTRIBUTE = 'attribute';
const MODEL_ATTRIBUTE_LIST = 'attributeList';
const MODEL_ATTRIBUTE_STRING = 'attributeString';
const MODEL_ATTRIBUTE_INTEGER= 'attributeInteger';
const MODEL_ATTRIBUTE_FLOAT= 'attributeFloat';
const MODEL_ATTRIBUTE_BOOLEAN= 'attributeBoolean';
const MODEL_ATTRIBUTE_EMAIL= 'attributeEmail';
const MODEL_ATTRIBUTE_IP= 'attributeIp';
const MODEL_ATTRIBUTE_URL= 'attributeUrl';
// Users
const MODEL_USER = 'user';
const MODEL_USER_LIST = 'userList';
@ -125,6 +159,7 @@ class Response extends SwooleResponse
// Deprecated
const MODEL_PERMISSIONS = 'permissions';
const MODEL_RULE = 'rule';
const MODEL_TASK = 'task';
// Tests (keep last)
const MODEL_MOCK = 'mock';
@ -154,7 +189,6 @@ class Response extends SwooleResponse
->setModel(new ErrorDev())
// Lists
->setModel(new BaseList('Collections List', self::MODEL_COLLECTION_LIST, 'collections', self::MODEL_COLLECTION))
->setModel(new BaseList('Attributes List', self::MODEL_ATTRIBUTE_LIST, 'attributes', self::MODEL_ATTRIBUTE))
->setModel(new BaseList('Indexes List', self::MODEL_INDEX_LIST, 'indexes', self::MODEL_INDEX))
->setModel(new BaseList('Documents List', self::MODEL_DOCUMENT_LIST, 'documents', self::MODEL_DOCUMENT))
->setModel(new BaseList('Users List', self::MODEL_USER_LIST, 'users', self::MODEL_USER))
@ -176,9 +210,18 @@ class Response extends SwooleResponse
->setModel(new BaseList('Languages List', self::MODEL_LANGUAGE_LIST, 'languages', self::MODEL_LANGUAGE))
->setModel(new BaseList('Currencies List', self::MODEL_CURRENCY_LIST, 'currencies', self::MODEL_CURRENCY))
->setModel(new BaseList('Phones List', self::MODEL_PHONE_LIST, 'phones', self::MODEL_PHONE))
->setModel(new BaseList('Metric List', self::MODEL_METRIC_LIST, 'metrics', self::MODEL_METRIC, true, false))
// Entities
->setModel(new Collection())
->setModel(new Attribute())
->setModel(new AttributeList())
->setModel(new AttributeString())
->setModel(new AttributeInteger())
->setModel(new AttributeFloat())
->setModel(new AttributeBoolean())
->setModel(new AttributeEmail())
->setModel(new AttributeIP())
->setModel(new AttributeURL())
->setModel(new Index())
->setModel(new ModelDocument())
->setModel(new Log())
@ -204,6 +247,14 @@ class Response extends SwooleResponse
->setModel(new Language())
->setModel(new Currency())
->setModel(new Phone())
->setModel(new Metric())
->setModel(new UsageDatabase())
->setModel(new UsageCollection())
->setModel(new UsageUsers())
->setModel(new UsageStorage())
->setModel(new UsageBuckets())
->setModel(new UsageFunctions())
->setModel(new UsageProject())
// Verification
// Recovery
// Tests (keep last)
@ -302,7 +353,7 @@ class Response extends SwooleResponse
$document = $model->filter($document);
foreach ($model->getRules() as $key => $rule) {
if (!$document->isSet($key)) {
if (!$document->isSet($key) && $rule['require']) { // do not set attribute in response if not required
if (!is_null($rule['default'])) {
$document->setAttribute($key, $rule['default']);
} else {
@ -317,15 +368,33 @@ class Response extends SwooleResponse
foreach ($data[$key] as &$item) {
if ($item instanceof Document) {
if (!array_key_exists($rule['type'], $this->models)) {
throw new Exception('Missing model for rule: '. $rule['type']);
if (\is_array($rule['type'])) {
foreach ($rule['type'] as $type) {
$condition = false;
foreach ($this->getModel($type)->conditions as $attribute => $val) {
$condition = $item->getAttribute($attribute) === $val;
if(!$condition) {
break;
}
}
if ($condition) {
$ruleType = $type;
break;
}
}
} else {
$ruleType = $rule['type'];
}
$item = $this->output($item, $rule['type']);
if (!array_key_exists($ruleType, $this->models)) {
throw new Exception('Missing model for rule: '. $ruleType);
}
$item = $this->output($item, $ruleType);
}
}
}
$output[$key] = $data[$key];
}

View file

@ -8,7 +8,7 @@ abstract class Model
{
const TYPE_STRING = 'string';
const TYPE_INTEGER = 'integer';
const TYPE_FLOAT = 'float';
const TYPE_FLOAT = 'double';
const TYPE_BOOLEAN = 'boolean';
const TYPE_JSON = 'json';
@ -35,7 +35,7 @@ abstract class Model
/**
* Filter Document Structure
*
* @return string
* @return Document
*/
public function filter(Document $document): Document
{
@ -68,6 +68,10 @@ abstract class Model
/**
* Add a New Rule
* If rule is an array of documents with varying models
*
* @param string $key
* @param array $options
*/
protected function addRule(string $key, array $options): self
{
@ -77,7 +81,7 @@ abstract class Model
'description' => '',
'default' => null,
'example' => '',
'array' => false,
'array' => false
], $options);
return $this;

View file

@ -10,17 +10,11 @@ class Attribute extends Model
public function __construct()
{
$this
->addRule('$collection', [
->addRule('key', [
'type' => self::TYPE_STRING,
'description' => 'Collection ID.',
'description' => 'Attribute Key.',
'default' => '',
'example' => '5e5ea5c16d55',
])
->addRule('$id', [
'type' => self::TYPE_STRING,
'description' => 'Attribute ID.',
'default' => '',
'example' => '60ccf71b98a2d',
'example' => 'fullName',
])
->addRule('type', [
'type' => self::TYPE_STRING,
@ -28,11 +22,11 @@ class Attribute extends Model
'default' => '',
'example' => 'string',
])
->addRule('size', [
->addRule('status', [
'type' => self::TYPE_STRING,
'description' => 'Attribute size.',
'default' => 0,
'example' => 128,
'description' => 'Attribute status. Possible values: `available`, `processing`, `deleting`, or `failed`',
'default' => '',
'example' => 'available',
])
->addRule('required', [
'type' => self::TYPE_BOOLEAN,
@ -45,11 +39,13 @@ class Attribute extends Model
'description' => 'Is attribute an array?',
'default' => false,
'example' => false,
'required' => false
'require' => false
])
;
}
public array $conditions = [];
/**
* Get Name
*
@ -69,4 +65,4 @@ class Attribute extends Model
{
return Response::MODEL_ATTRIBUTE;
}
}
}

View file

@ -0,0 +1,49 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model\Attribute;
class AttributeBoolean extends Attribute
{
public function __construct()
{
parent::__construct();
$this
->addRule('default', [
'type' => self::TYPE_BOOLEAN,
'description' => 'Default value for attribute when not provided. Cannot be set when attribute is required.',
'default' => null,
'example' => false,
'array' => false,
'require' => false,
])
;
}
public array $conditions = [
'type' => self::TYPE_BOOLEAN
];
/**
* Get Name
*
* @return string
*/
public function getName():string
{
return 'AttributeBoolean';
}
/**
* Get Type
*
* @return string
*/
public function getType():string
{
return Response::MODEL_ATTRIBUTE_BOOLEAN;
}
}

View file

@ -0,0 +1,58 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model\Attribute;
class AttributeEmail extends Attribute
{
public function __construct()
{
parent::__construct();
$this
->addRule('format', [
'type' => self::TYPE_STRING,
'description' => 'String format.',
'default' => APP_DATABASE_ATTRIBUTE_EMAIL,
'example' => APP_DATABASE_ATTRIBUTE_EMAIL,
'array' => false,
'require' => true,
])
->addRule('default', [
'type' => self::TYPE_STRING,
'description' => 'Default value for attribute when not provided. Cannot be set when attribute is required.',
'default' => null,
'example' => 'default@example.com',
'array' => false,
'require' => false,
])
;
}
public array $conditions = [
'type' => self::TYPE_STRING,
'format' => \APP_DATABASE_ATTRIBUTE_EMAIL
];
/**
* Get Name
*
* @return string
*/
public function getName():string
{
return 'AttributeEmail';
}
/**
* Get Type
*
* @return string
*/
public function getType():string
{
return Response::MODEL_ATTRIBUTE_EMAIL;
}
}

View file

@ -0,0 +1,65 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model\Attribute;
class AttributeFloat extends Attribute
{
public function __construct()
{
parent::__construct();
$this
->addRule('min', [
'type' => self::TYPE_FLOAT,
'description' => 'Minimum value to enforce for new documents.',
'default' => null,
'example' => 1.5,
'array' => false,
'require' => false,
])
->addRule('max', [
'type' => self::TYPE_FLOAT,
'description' => 'Maximum value to enforce for new documents.',
'default' => null,
'example' => 10.5,
'array' => false,
'require' => false,
])
->addRule('default', [
'type' => self::TYPE_FLOAT,
'description' => 'Default value for attribute when not provided. Cannot be set when attribute is required.',
'default' => null,
'example' => 2.5,
'array' => false,
'require' => false,
])
;
}
public array $conditions = [
'type' => self::TYPE_FLOAT,
];
/**
* Get Name
*
* @return string
*/
public function getName():string
{
return 'AttributeFloat';
}
/**
* Get Type
*
* @return string
*/
public function getType():string
{
return Response::MODEL_ATTRIBUTE_FLOAT;
}
}

View file

@ -0,0 +1,58 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model\Attribute;
class AttributeIP extends Attribute
{
public function __construct()
{
parent::__construct();
$this
->addRule('format', [
'type' => self::TYPE_STRING,
'description' => 'String format.',
'default' => APP_DATABASE_ATTRIBUTE_IP,
'example' => APP_DATABASE_ATTRIBUTE_IP,
'array' => false,
'require' => true,
])
->addRule('default', [
'type' => self::TYPE_STRING,
'description' => 'Default value for attribute when not provided. Cannot be set when attribute is required.',
'default' => null,
'example' => '192.0.2.0',
'array' => false,
'require' => false,
])
;
}
public array $conditions = [
'type' => self::TYPE_STRING,
'format' => \APP_DATABASE_ATTRIBUTE_IP
];
/**
* Get Name
*
* @return string
*/
public function getName():string
{
return 'AttributeIP';
}
/**
* Get Type
*
* @return string
*/
public function getType():string
{
return Response::MODEL_ATTRIBUTE_IP;
}
}

View file

@ -0,0 +1,64 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model\Attribute;
class AttributeInteger extends Attribute
{
public function __construct()
{
parent::__construct();
$this
->addRule('min', [
'type' => self::TYPE_INTEGER,
'description' => 'Minimum value to enforce for new documents.',
'default' => null,
'example' => 1,
'array' => false,
'require' => false,
])
->addRule('max', [
'type' => self::TYPE_INTEGER,
'description' => 'Maximum value to enforce for new documents.',
'default' => null,
'example' => 10,
'array' => false,
'require' => false,
])
->addRule('default', [
'type' => self::TYPE_INTEGER,
'description' => 'Default value for attribute when not provided. Cannot be set when attribute is required.',
'default' => null,
'example' => 10,
'array' => false,
'require' => false,
])
;
}
public array $conditions = [
'type' => self::TYPE_INTEGER,
];
/**
* Get Name *
* @return string
*/
public function getName():string
{
return 'AttributeInteger';
}
/**
* Get Type
*
* @return string
*/
public function getType():string
{
return Response::MODEL_ATTRIBUTE_INTEGER;
}
}

View file

@ -0,0 +1,56 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model;
use Utopia\Database\Document;
class AttributeList extends Model
{
public function __construct()
{
$this
->addRule('sum', [
'type' => self::TYPE_INTEGER,
'description' => 'Total sum of items in the list.',
'default' => 0,
'example' => 5,
])
->addRule('attributes', [
'type' => [
Response::MODEL_ATTRIBUTE_BOOLEAN,
Response::MODEL_ATTRIBUTE_INTEGER,
Response::MODEL_ATTRIBUTE_FLOAT,
Response::MODEL_ATTRIBUTE_EMAIL,
Response::MODEL_ATTRIBUTE_URL,
Response::MODEL_ATTRIBUTE_IP,
Response::MODEL_ATTRIBUTE_STRING // needs to be last, since its condition would dominate any other string attribute
],
'description' => 'List of attributes.',
'default' => [],
'array' => true
])
;
}
/**
* Get Name
*
* @return string
*/
public function getName():string
{
return 'Attributes List';
}
/**
* Get Type
*
* @return string
*/
public function getType():string
{
return Response::MODEL_ATTRIBUTE_LIST;
}
}

View file

@ -0,0 +1,55 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model\Attribute;
class AttributeString extends Attribute
{
public function __construct()
{
parent::__construct();
$this
->addRule('size', [
'type' => self::TYPE_STRING,
'description' => 'Attribute size.',
'default' => 0,
'example' => 128,
])
->addRule('default', [
'type' => self::TYPE_STRING,
'description' => 'Default value for attribute when not provided. Cannot be set when attribute is required.',
'default' => null,
'example' => 'default',
'array' => false,
'require' => false,
])
;
}
public array $conditions = [
'type' => self::TYPE_STRING,
];
/**
* Get Name
*
* @return string
*/
public function getName():string
{
return 'AttributeString';
}
/**
* Get Type
*
* @return string
*/
public function getType():string
{
return Response::MODEL_ATTRIBUTE_STRING;
}
}

View file

@ -0,0 +1,58 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model\Attribute;
class AttributeURL extends Attribute
{
public function __construct()
{
parent::__construct();
$this
->addRule('format', [
'type' => self::TYPE_STRING,
'description' => 'String format.',
'default' => APP_DATABASE_ATTRIBUTE_URL,
'example' => APP_DATABASE_ATTRIBUTE_URL,
'array' => false,
'required' => true,
])
->addRule('default', [
'type' => self::TYPE_STRING,
'description' => 'Default value for attribute when not provided. Cannot be set when attribute is required.',
'default' => null,
'example' => 'http://example.com',
'array' => false,
'require' => false,
])
;
}
public array $conditions = [
'type' => self::TYPE_STRING,
'format' => \APP_DATABASE_ATTRIBUTE_URL
];
/**
* Get Name
*
* @return string
*/
public function getName():string
{
return 'AttributeURL';
}
/**
* Get Type
*
* @return string
*/
public function getType():string
{
return Response::MODEL_ATTRIBUTE_URL;
}
}

View file

@ -4,7 +4,7 @@ namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model;
use stdClass;
use Utopia\Database\Document;
class Collection extends Model
{
@ -35,14 +35,28 @@ class Collection extends Model
'type' => self::TYPE_STRING,
'description' => 'Collection name.',
'default' => '',
'example' => '',
'example' => 'My Collection',
])
->addRule('permission', [
'type' => self::TYPE_STRING,
'description' => 'Collection permission model. Possible values: `document` or `collection`',
'default' => '',
'example' => 'document',
])
->addRule('attributes', [
'type' => Response::MODEL_ATTRIBUTE,
'type' => [
Response::MODEL_ATTRIBUTE_BOOLEAN,
Response::MODEL_ATTRIBUTE_INTEGER,
Response::MODEL_ATTRIBUTE_FLOAT,
Response::MODEL_ATTRIBUTE_EMAIL,
Response::MODEL_ATTRIBUTE_URL,
Response::MODEL_ATTRIBUTE_IP,
Response::MODEL_ATTRIBUTE_STRING, // needs to be last, since its condition would dominate any other string attribute
],
'description' => 'Collection attributes.',
'default' => [],
'example' => new stdClass,
'array' => true
'array' => true,
])
->addRule('indexes', [
'type' => Response::MODEL_INDEX,
@ -51,20 +65,6 @@ class Collection extends Model
'example' => new stdClass,
'array' => true
])
->addRule('attributesInQueue', [
'type' => Response::MODEL_ATTRIBUTE,
'description' => 'Collection attributes in creation queue.',
'default' => [],
'example' => new stdClass,
'array' => true
])
->addRule('indexesInQueue', [
'type' => Response::MODEL_INDEX,
'description' => 'Collection indexes in creation queue.',
'default' => [],
'example' => new stdClass,
'array' => true
])
;
}

View file

@ -43,7 +43,7 @@ class Func extends Model
])
->addRule('status', [
'type' => self::TYPE_STRING,
'description' => 'Function status. Possible values: disabled, enabled',
'description' => 'Function status. Possible values: `disabled`, `enabled`',
'default' => '',
'example' => 'enabled',
])

View file

@ -10,11 +10,11 @@ class Index extends Model
public function __construct()
{
$this
->addRule('$id', [
->addRule('key', [
'type' => self::TYPE_STRING,
'description' => 'Index ID.',
'description' => 'Index Key.',
'default' => '',
'example' => '',
'example' => 'index1',
])
->addRule('type', [
'type' => self::TYPE_STRING,
@ -22,6 +22,12 @@ class Index extends Model
'default' => '',
'example' => '',
])
->addRule('status', [
'type' => self::TYPE_STRING,
'description' => 'Index status. Possible values: `available`, `processing`, `deleting`, or `failed`',
'default' => '',
'example' => 'available',
])
->addRule('attributes', [
'type' => self::TYPE_STRING,
'description' => 'Index attributes.',

View file

@ -0,0 +1,47 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model;
class Metric extends Model
{
public function __construct()
{
$this
->addRule('value', [
'type' => self::TYPE_INTEGER,
'description' => 'The value of this metric at the timestamp.',
'default' => -1,
'example' => 1,
])
->addRule('timestamp', [
'type' => self::TYPE_INTEGER,
'description' => 'The UNIX timestamp at which this metric was aggregated.',
'default' => 0,
'example' => 1592981250
])
;
}
/**
* Get Name
*
* @return string
*/
public function getName():string
{
return 'Metric';
}
/**
* Get Collection
*
* @return string
*/
public function getType():string
{
return Response::MODEL_METRIC;
}
}

View file

@ -0,0 +1,77 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model;
use stdClass;
class UsageBuckets extends Model
{
public function __construct()
{
$this
->addRule('range', [
'type' => self::TYPE_STRING,
'description' => 'The time range of the usage stats.',
'default' => '',
'example' => '30d',
])
->addRule('files.count', [
'type' => Response::MODEL_METRIC_LIST,
'description' => 'Aggregated stats for total number of files in this bucket.',
'default' => [],
'example' => new stdClass,
'array' => true
])
->addRule('files.create', [
'type' => Response::MODEL_METRIC_LIST,
'description' => 'Aggregated stats for files created.',
'default' => [],
'example' => new stdClass,
'array' => true
])
->addRule('files.read', [
'type' => Response::MODEL_METRIC_LIST,
'description' => 'Aggregated stats for files read.',
'default' => [],
'example' => new stdClass,
'array' => true
])
->addRule('files.update', [
'type' => Response::MODEL_METRIC_LIST,
'description' => 'Aggregated stats for files updated.',
'default' => [],
'example' => new stdClass,
'array' => true
])
->addRule('files.delete', [
'type' => Response::MODEL_METRIC_LIST,
'description' => 'Aggregated stats for files deleted.',
'default' => [],
'example' => new stdClass,
'array' => true
])
;
}
/**
* Get Name
*
* @return string
*/
public function getName():string
{
return 'UsageBuckets';
}
/**
* Get Type
*
* @return string
*/
public function getType():string
{
return Response::MODEL_USAGE_BUCKETS;
}
}

View file

@ -0,0 +1,77 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model;
use stdClass;
class UsageCollection extends Model
{
public function __construct()
{
$this
->addRule('range', [
'type' => self::TYPE_STRING,
'description' => 'The time range of the usage stats.',
'default' => '',
'example' => '30d',
])
->addRule('documents.count', [
'type' => Response::MODEL_METRIC_LIST,
'description' => 'Aggregated stats for total number of documents.',
'default' => [],
'example' => new stdClass,
'array' => true
])
->addRule('documents.create', [
'type' => Response::MODEL_METRIC_LIST,
'description' => 'Aggregated stats for documents created.',
'default' => [],
'example' => new stdClass,
'array' => true
])
->addRule('documents.read', [
'type' => Response::MODEL_METRIC_LIST,
'description' => 'Aggregated stats for documents read.',
'default' => [],
'example' => new stdClass,
'array' => true
])
->addRule('documents.update', [
'type' => Response::MODEL_METRIC_LIST,
'description' => 'Aggregated stats for documents updated.',
'default' => [],
'example' => new stdClass,
'array' => true
])
->addRule('documents.delete', [
'type' => Response::MODEL_METRIC_LIST,
'description' => 'Aggregated stats for documents deleted.',
'default' => [],
'example' => new stdClass,
'array' => true
])
;
}
/**
* Get Name
*
* @return string
*/
public function getName():string
{
return 'UsageCollection';
}
/**
* Get Type
*
* @return string
*/
public function getType():string
{
return Response::MODEL_USAGE_COLLECTION;
}
}

View file

@ -0,0 +1,112 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model;
use stdClass;
class UsageDatabase extends Model
{
public function __construct()
{
$this
->addRule('range', [
'type' => self::TYPE_STRING,
'description' => 'The time range of the usage stats.',
'default' => '',
'example' => '30d',
])
->addRule('documents.count', [
'type' => Response::MODEL_METRIC_LIST,
'description' => 'Aggregated stats for total number of documents.',
'default' => [],
'example' => new stdClass,
'array' => true
])
->addRule('collections.count', [
'type' => Response::MODEL_METRIC_LIST,
'description' => 'Aggregated stats for total number of collections.',
'default' => [],
'example' => new stdClass,
'array' => true
])
->addRule('documents.create', [
'type' => Response::MODEL_METRIC_LIST,
'description' => 'Aggregated stats for documents created.',
'default' => [],
'example' => new stdClass,
'array' => true
])
->addRule('documents.read', [
'type' => Response::MODEL_METRIC_LIST,
'description' => 'Aggregated stats for documents read.',
'default' => [],
'example' => new stdClass,
'array' => true
])
->addRule('documents.update', [
'type' => Response::MODEL_METRIC_LIST,
'description' => 'Aggregated stats for documents updated.',
'default' => [],
'example' => new stdClass,
'array' => true
])
->addRule('documents.delete', [
'type' => Response::MODEL_METRIC_LIST,
'description' => 'Aggregated stats for documents deleted.',
'default' => [],
'example' => new stdClass,
'array' => true
])
->addRule('collections.create', [
'type' => Response::MODEL_METRIC_LIST,
'description' => 'Aggregated stats for collections created.',
'default' => [],
'example' => new stdClass,
'array' => true
])
->addRule('collections.read', [
'type' => Response::MODEL_METRIC_LIST,
'description' => 'Aggregated stats for collections read.',
'default' => [],
'example' => new stdClass,
'array' => true
])
->addRule('collections.update', [
'type' => Response::MODEL_METRIC_LIST,
'description' => 'Aggregated stats for collections updated.',
'default' => [],
'example' => new stdClass,
'array' => true
])
->addRule('collections.delete', [
'type' => Response::MODEL_METRIC_LIST,
'description' => 'Aggregated stats for collections delete.',
'default' => [],
'example' => new stdClass,
'array' => true
])
;
}
/**
* Get Name
*
* @return string
*/
public function getName():string
{
return 'UsageDatabase';
}
/**
* Get Type
*
* @return string
*/
public function getType():string
{
return Response::MODEL_USAGE_DATABASE;
}
}

View file

@ -0,0 +1,63 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model;
use stdClass;
class UsageFunctions extends Model
{
public function __construct()
{
$this
->addRule('range', [
'type' => self::TYPE_STRING,
'description' => 'The time range of the usage stats.',
'default' => '',
'example' => '30d',
])
->addRule('functions.executions', [
'type' => Response::MODEL_METRIC_LIST,
'description' => 'Aggregated stats for function executions.',
'default' => [],
'example' => new stdClass,
'array' => true
])
->addRule('functions.failures', [
'type' => Response::MODEL_METRIC_LIST,
'description' => 'Aggregated stats for function execution failures.',
'default' => [],
'example' => new stdClass,
'array' => true
])
->addRule('functions.compute', [
'type' => Response::MODEL_METRIC_LIST,
'description' => 'Aggregated stats for function execution duration.',
'default' => [],
'example' => new stdClass,
'array' => true
])
;
}
/**
* Get Name
*
* @return string
*/
public function getName():string
{
return 'UsageFunctions';
}
/**
* Get Type
*
* @return string
*/
public function getType():string
{
return Response::MODEL_USAGE_FUNCTIONS;
}
}

View file

@ -0,0 +1,91 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model;
use stdClass;
class UsageProject extends Model
{
public function __construct()
{
$this
->addRule('range', [
'type' => self::TYPE_STRING,
'description' => 'The time range of the usage stats.',
'default' => '',
'example' => '30d',
])
->addRule('requests', [
'type' => Response::MODEL_METRIC_LIST,
'description' => 'Aggregated stats for number of requests.',
'default' => [],
'example' => new stdClass,
'array' => true
])
->addRule('network', [
'type' => Response::MODEL_METRIC_LIST,
'description' => 'Aggregated stats for consumed bandwidth.',
'default' => [],
'example' => new stdClass,
'array' => true
])
->addRule('functions', [
'type' => Response::MODEL_METRIC_LIST,
'description' => 'Aggregated stats for function executions.',
'default' => [],
'example' => new stdClass,
'array' => true
])
->addRule('documents', [
'type' => Response::MODEL_METRIC_LIST,
'description' => 'Aggregated stats for number of documents.',
'default' => [],
'example' => new stdClass,
'array' => true
])
->addRule('collections', [
'type' => Response::MODEL_METRIC_LIST,
'description' => 'Aggregated stats for number of collections.',
'default' => [],
'example' => new stdClass,
'array' => true
])
->addRule('users', [
'type' => Response::MODEL_METRIC_LIST,
'description' => 'Aggregated stats for number of users.',
'default' => [],
'example' => new stdClass,
'array' => true
])
->addRule('storage', [
'type' => Response::MODEL_METRIC_LIST,
'description' => 'Aggregated stats for the occupied storage size (in bytes).',
'default' => [],
'example' => new stdClass,
'array' => true
])
;
}
/**
* Get Name
*
* @return string
*/
public function getName():string
{
return 'UsageProject';
}
/**
* Get Type
*
* @return string
*/
public function getType():string
{
return Response::MODEL_USAGE_PROJECT;
}
}

View file

@ -0,0 +1,56 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model;
use stdClass;
class UsageStorage extends Model
{
public function __construct()
{
$this
->addRule('range', [
'type' => self::TYPE_STRING,
'description' => 'The time range of the usage stats.',
'default' => '',
'example' => '30d',
])
->addRule('storage', [
'type' => Response::MODEL_METRIC_LIST,
'description' => 'Aggregated stats for the occupied storage size (in bytes).',
'default' => [],
'example' => new stdClass,
'array' => true
])
->addRule('files', [
'type' => Response::MODEL_METRIC_LIST,
'description' => 'Aggregated stats for total number of files.',
'default' => [],
'example' => new stdClass,
'array' => true
])
;
}
/**
* Get Name
*
* @return string
*/
public function getName():string
{
return 'StorageUsage';
}
/**
* Get Type
*
* @return string
*/
public function getType():string
{
return Response::MODEL_USAGE_STORAGE;
}
}

View file

@ -0,0 +1,98 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model;
use stdClass;
class UsageUsers extends Model
{
public function __construct()
{
$this
->addRule('range', [
'type' => self::TYPE_STRING,
'description' => 'The time range of the usage stats.',
'default' => '',
'example' => '30d',
])
->addRule('users.count', [
'type' => Response::MODEL_METRIC_LIST,
'description' => 'Aggregated stats for total number of users.',
'default' => [],
'example' => new stdClass,
'array' => true
])
->addRule('users.create', [
'type' => Response::MODEL_METRIC_LIST,
'description' => 'Aggregated stats for users created.',
'default' => [],
'example' => new stdClass,
'array' => true
])
->addRule('users.read', [
'type' => Response::MODEL_METRIC_LIST,
'description' => 'Aggregated stats for users read.',
'default' => [],
'example' => new stdClass,
'array' => true
])
->addRule('users.update', [
'type' => Response::MODEL_METRIC_LIST,
'description' => 'Aggregated stats for users updated.',
'default' => [],
'example' => new stdClass,
'array' => true
])
->addRule('users.delete', [
'type' => Response::MODEL_METRIC_LIST,
'description' => 'Aggregated stats for users deleted.',
'default' => [],
'example' => new stdClass,
'array' => true
])
->addRule('sessions.create', [
'type' => Response::MODEL_METRIC_LIST,
'description' => 'Aggregated stats for sessions created.',
'default' => [],
'example' => new stdClass,
'array' => true
])
->addRule('sessions.provider.create', [
'type' => Response::MODEL_METRIC_LIST,
'description' => 'Aggregated stats for sessions created for a provider ( email, anonymous or oauth2 ).',
'default' => [],
'example' => new stdClass,
'array' => true
])
->addRule('sessions.delete', [
'type' => Response::MODEL_METRIC_LIST,
'description' => 'Aggregated stats for sessions deleted.',
'default' => [],
'example' => new stdClass,
'array' => true
])
;
}
/**
* Get Name
*
* @return string
*/
public function getName():string
{
return 'UsageUsers';
}
/**
* Get Type
*
* @return string
*/
public function getType():string
{
return Response::MODEL_USAGE_USERS;
}
}

View file

@ -0,0 +1,23 @@
<?php
namespace Tests\E2E\Scopes;
trait SideConsole
{
public function getHeaders():array
{
return [
'origin' => 'http://localhost',
'cookie' => 'a_session_console='. $this->getRoot()['session'],
'x-appwrite-mode' => 'admin'
];
}
/**
* @return string
*/
public function getSide()
{
return 'console';
}
}

View file

@ -342,7 +342,7 @@ class AccountCustomClientTest extends Scope
'password' => $password,
]);
$this->assertEquals($response['headers']['status-code'], 400);
$this->assertEquals($response['headers']['status-code'], 409);
/**
* Test for SUCCESS

View file

@ -20,6 +20,7 @@ trait DatabaseBase
'name' => 'Movies',
'read' => ['role:all'],
'write' => ['role:all'],
'permission' => 'document',
]);
$this->assertEquals($movies['headers']['status-code'], 201);
@ -43,8 +44,6 @@ trait DatabaseBase
'required' => true,
]);
sleep(2);
$releaseYear = $this->client->call(Client::METHOD_POST, '/database/collections/' . $data['moviesId'] . '/attributes/integer', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
@ -54,8 +53,6 @@ trait DatabaseBase
'required' => true,
]);
sleep(2);
$actors = $this->client->call(Client::METHOD_POST, '/database/collections/' . $data['moviesId'] . '/attributes/string', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
@ -68,26 +65,25 @@ trait DatabaseBase
]);
$this->assertEquals($title['headers']['status-code'], 201);
$this->assertEquals($title['body']['$id'], 'title');
$this->assertEquals($title['body']['key'], 'title');
$this->assertEquals($title['body']['type'], 'string');
$this->assertEquals($title['body']['size'], 256);
$this->assertEquals($title['body']['required'], true);
$this->assertEquals($releaseYear['headers']['status-code'], 201);
$this->assertEquals($releaseYear['body']['$id'], 'releaseYear');
$this->assertEquals($releaseYear['body']['key'], 'releaseYear');
$this->assertEquals($releaseYear['body']['type'], 'integer');
$this->assertEquals($releaseYear['body']['size'], 0);
$this->assertEquals($releaseYear['body']['required'], true);
$this->assertEquals($actors['headers']['status-code'], 201);
$this->assertEquals($actors['body']['$id'], 'actors');
$this->assertEquals($actors['body']['key'], 'actors');
$this->assertEquals($actors['body']['type'], 'string');
$this->assertEquals($actors['body']['size'], 256);
$this->assertEquals($actors['body']['required'], false);
$this->assertEquals($actors['body']['array'], true);
// wait for database worker to create attributes
sleep(5);
sleep(2);
$movies = $this->client->call(Client::METHOD_GET, '/database/collections/' . $data['moviesId'], array_merge([
'content-type' => 'application/json',
@ -95,13 +91,425 @@ trait DatabaseBase
'x-appwrite-key' => $this->getProject()['apiKey']
]), []);
$this->assertIsArray($movies['body']['attributesInQueue']);
$this->assertCount(0, $movies['body']['attributesInQueue']);
$this->assertIsArray($movies['body']['attributes']);
$this->assertCount(3, $movies['body']['attributes']);
$this->assertEquals($movies['body']['attributes'][0]['$id'], $title['body']['$id']);
$this->assertEquals($movies['body']['attributes'][1]['$id'], $releaseYear['body']['$id']);
$this->assertEquals($movies['body']['attributes'][2]['$id'], $actors['body']['$id']);
$this->assertEquals($movies['body']['attributes'][0]['key'], $title['body']['key']);
$this->assertEquals($movies['body']['attributes'][1]['key'], $releaseYear['body']['key']);
$this->assertEquals($movies['body']['attributes'][2]['key'], $actors['body']['key']);
return $data;
}
/**
* @depends testCreateAttributes
*/
public function testAttributeResponseModels(array $data): array
{
$collection= $this->client->call(Client::METHOD_POST, '/database/collections', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'collectionId' => 'unique()',
'name' => 'Response Models',
'read' => ['role:all'],
'write' => ['role:all'],
'permission' => 'document',
]);
$this->assertEquals($collection['headers']['status-code'], 201);
$this->assertEquals($collection['body']['name'], 'Response Models');
$collectionId = $collection['body']['$id'];
$string = $this->client->call(Client::METHOD_POST, '/database/collections/' . $collectionId . '/attributes/string', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'attributeId' => 'string',
'size' => 16,
'required' => false,
'default' => 'default',
]);
$email = $this->client->call(Client::METHOD_POST, '/database/collections/' . $collectionId . '/attributes/email', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'attributeId' => 'email',
'required' => false,
'default' => 'default@example.com',
]);
$ip = $this->client->call(Client::METHOD_POST, '/database/collections/' . $collectionId . '/attributes/ip', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'attributeId' => 'ip',
'required' => false,
'default' => '192.0.2.0',
]);
$url = $this->client->call(Client::METHOD_POST, '/database/collections/' . $collectionId . '/attributes/url', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'attributeId' => 'url',
'required' => false,
'default' => 'http://example.com',
]);
$integer = $this->client->call(Client::METHOD_POST, '/database/collections/' . $collectionId . '/attributes/integer', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'attributeId' => 'integer',
'required' => false,
'min' => 1,
'max' => 5,
'default' => 3
]);
$float = $this->client->call(Client::METHOD_POST, '/database/collections/' . $collectionId . '/attributes/float', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'attributeId' => 'float',
'required' => false,
'min' => 1.5,
'max' => 5.5,
'default' => 3.5
]);
$boolean = $this->client->call(Client::METHOD_POST, '/database/collections/' . $collectionId . '/attributes/boolean', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'attributeId' => 'boolean',
'required' => false,
'default' => true,
]);
$this->assertEquals(201, $string['headers']['status-code']);
$this->assertEquals('string', $string['body']['key']);
$this->assertEquals('string', $string['body']['type']);
$this->assertEquals('processing', $string['body']['status']);
$this->assertEquals(false, $string['body']['required']);
$this->assertEquals(false, $string['body']['array']);
$this->assertEquals(16, $string['body']['size']);
$this->assertEquals('default', $string['body']['default']);
$this->assertEquals(201, $email['headers']['status-code']);
$this->assertEquals('email', $email['body']['key']);
$this->assertEquals('string', $email['body']['type']);
$this->assertEquals('processing', $email['body']['status']);
$this->assertEquals(false, $email['body']['required']);
$this->assertEquals(false, $email['body']['array']);
$this->assertEquals('email', $email['body']['format']);
$this->assertEquals('default@example.com', $email['body']['default']);
$this->assertEquals(201, $ip['headers']['status-code']);
$this->assertEquals('ip', $ip['body']['key']);
$this->assertEquals('string', $ip['body']['type']);
$this->assertEquals('processing', $ip['body']['status']);
$this->assertEquals(false, $ip['body']['required']);
$this->assertEquals(false, $ip['body']['array']);
$this->assertEquals('ip', $ip['body']['format']);
$this->assertEquals('192.0.2.0', $ip['body']['default']);
$this->assertEquals(201, $url['headers']['status-code']);
$this->assertEquals('url', $url['body']['key']);
$this->assertEquals('string', $url['body']['type']);
$this->assertEquals('processing', $url['body']['status']);
$this->assertEquals(false, $url['body']['required']);
$this->assertEquals(false, $url['body']['array']);
$this->assertEquals('url', $url['body']['format']);
$this->assertEquals('http://example.com', $url['body']['default']);
$this->assertEquals(201, $integer['headers']['status-code']);
$this->assertEquals('integer', $integer['body']['key']);
$this->assertEquals('integer', $integer['body']['type']);
$this->assertEquals('processing', $integer['body']['status']);
$this->assertEquals(false, $integer['body']['required']);
$this->assertEquals(false, $integer['body']['array']);
$this->assertEquals(1, $integer['body']['min']);
$this->assertEquals(5, $integer['body']['max']);
$this->assertEquals(3, $integer['body']['default']);
$this->assertEquals(201, $float['headers']['status-code']);
$this->assertEquals('float', $float['body']['key']);
$this->assertEquals('double', $float['body']['type']);
$this->assertEquals('processing', $float['body']['status']);
$this->assertEquals(false, $float['body']['required']);
$this->assertEquals(false, $float['body']['array']);
$this->assertEquals(1.5, $float['body']['min']);
$this->assertEquals(5.5, $float['body']['max']);
$this->assertEquals(3.5, $float['body']['default']);
$this->assertEquals(201, $boolean['headers']['status-code']);
$this->assertEquals('boolean', $boolean['body']['key']);
$this->assertEquals('boolean', $boolean['body']['type']);
$this->assertEquals('processing', $boolean['body']['status']);
$this->assertEquals(false, $boolean['body']['required']);
$this->assertEquals(false, $boolean['body']['array']);
$this->assertEquals(true, $boolean['body']['default']);
// wait for database worker to create attributes
sleep(5);
$stringResponse = $this->client->call(Client::METHOD_GET, "/database/collections/{$collectionId}/attributes/{$collectionId}_{$string['body']['key']}",array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]));
$emailResponse = $this->client->call(Client::METHOD_GET, "/database/collections/{$collectionId}/attributes/{$collectionId}_{$email['body']['key']}",array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]));
$ipResponse = $this->client->call(Client::METHOD_GET, "/database/collections/{$collectionId}/attributes/{$collectionId}_{$ip['body']['key']}",array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]));
$urlResponse = $this->client->call(Client::METHOD_GET, "/database/collections/{$collectionId}/attributes/{$collectionId}_{$url['body']['key']}",array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]));
$integerResponse = $this->client->call(Client::METHOD_GET, "/database/collections/{$collectionId}/attributes/{$collectionId}_{$integer['body']['key']}",array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]));
$floatResponse = $this->client->call(Client::METHOD_GET, "/database/collections/{$collectionId}/attributes/{$collectionId}_{$float['body']['key']}",array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]));
$booleanResponse = $this->client->call(Client::METHOD_GET, "/database/collections/{$collectionId}/attributes/{$collectionId}_{$boolean['body']['key']}",array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]));
$this->assertEquals(200, $stringResponse['headers']['status-code']);
$this->assertEquals($string['body']['key'], $stringResponse['body']['key']);
$this->assertEquals($string['body']['type'], $stringResponse['body']['type']);
$this->assertEquals('available', $stringResponse['body']['status']);
$this->assertEquals($string['body']['required'], $stringResponse['body']['required']);
$this->assertEquals($string['body']['array'], $stringResponse['body']['array']);
$this->assertEquals(16, $stringResponse['body']['size']);
$this->assertEquals($string['body']['default'], $stringResponse['body']['default']);
$this->assertEquals(200, $emailResponse['headers']['status-code']);
$this->assertEquals($email['body']['key'], $emailResponse['body']['key']);
$this->assertEquals($email['body']['type'], $emailResponse['body']['type']);
$this->assertEquals('available', $emailResponse['body']['status']);
$this->assertEquals($email['body']['required'], $emailResponse['body']['required']);
$this->assertEquals($email['body']['array'], $emailResponse['body']['array']);
$this->assertEquals($email['body']['format'], $emailResponse['body']['format']);
$this->assertEquals($email['body']['default'], $emailResponse['body']['default']);
$this->assertEquals(200, $ipResponse['headers']['status-code']);
$this->assertEquals($ip['body']['key'], $ipResponse['body']['key']);
$this->assertEquals($ip['body']['type'], $ipResponse['body']['type']);
$this->assertEquals('available', $ipResponse['body']['status']);
$this->assertEquals($ip['body']['required'], $ipResponse['body']['required']);
$this->assertEquals($ip['body']['array'], $ipResponse['body']['array']);
$this->assertEquals($ip['body']['format'], $ipResponse['body']['format']);
$this->assertEquals($ip['body']['default'], $ipResponse['body']['default']);
$this->assertEquals(200, $urlResponse['headers']['status-code']);
$this->assertEquals($url['body']['key'], $urlResponse['body']['key']);
$this->assertEquals($url['body']['type'], $urlResponse['body']['type']);
$this->assertEquals('available', $urlResponse['body']['status']);
$this->assertEquals($url['body']['required'], $urlResponse['body']['required']);
$this->assertEquals($url['body']['array'], $urlResponse['body']['array']);
$this->assertEquals($url['body']['format'], $urlResponse['body']['format']);
$this->assertEquals($url['body']['default'], $urlResponse['body']['default']);
$this->assertEquals(200, $integerResponse['headers']['status-code']);
$this->assertEquals($integer['body']['key'], $integerResponse['body']['key']);
$this->assertEquals($integer['body']['type'], $integerResponse['body']['type']);
$this->assertEquals('available', $integerResponse['body']['status']);
$this->assertEquals($integer['body']['required'], $integerResponse['body']['required']);
$this->assertEquals($integer['body']['array'], $integerResponse['body']['array']);
$this->assertEquals($integer['body']['min'], $integerResponse['body']['min']);
$this->assertEquals($integer['body']['max'], $integerResponse['body']['max']);
$this->assertEquals($integer['body']['default'], $integerResponse['body']['default']);
$this->assertEquals(200, $floatResponse['headers']['status-code']);
$this->assertEquals($float['body']['key'], $floatResponse['body']['key']);
$this->assertEquals($float['body']['type'], $floatResponse['body']['type']);
$this->assertEquals('available', $floatResponse['body']['status']);
$this->assertEquals($float['body']['required'], $floatResponse['body']['required']);
$this->assertEquals($float['body']['array'], $floatResponse['body']['array']);
$this->assertEquals($float['body']['min'], $floatResponse['body']['min']);
$this->assertEquals($float['body']['max'], $floatResponse['body']['max']);
$this->assertEquals($float['body']['default'], $floatResponse['body']['default']);
$this->assertEquals(200, $booleanResponse['headers']['status-code']);
$this->assertEquals($boolean['body']['key'], $booleanResponse['body']['key']);
$this->assertEquals($boolean['body']['type'], $booleanResponse['body']['type']);
$this->assertEquals('available', $booleanResponse['body']['status']);
$this->assertEquals($boolean['body']['required'], $booleanResponse['body']['required']);
$this->assertEquals($boolean['body']['array'], $booleanResponse['body']['array']);
$this->assertEquals($boolean['body']['default'], $booleanResponse['body']['default']);
$attributes = $this->client->call(Client::METHOD_GET, '/database/collections/' . $collectionId . '/attributes', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]));
$this->assertEquals(200, $attributes['headers']['status-code']);
$this->assertEquals(7, $attributes['body']['sum']);
$attributes = $attributes['body']['attributes'];
$this->assertIsArray($attributes);
$this->assertCount(7, $attributes);
$this->assertEquals($stringResponse['body']['key'], $attributes[0]['key']);
$this->assertEquals($stringResponse['body']['type'], $attributes[0]['type']);
$this->assertEquals($stringResponse['body']['status'], $attributes[0]['status']);
$this->assertEquals($stringResponse['body']['required'], $attributes[0]['required']);
$this->assertEquals($stringResponse['body']['array'], $attributes[0]['array']);
$this->assertEquals($stringResponse['body']['size'], $attributes[0]['size']);
$this->assertEquals($stringResponse['body']['default'], $attributes[0]['default']);
$this->assertEquals($emailResponse['body']['key'], $attributes[1]['key']);
$this->assertEquals($emailResponse['body']['type'], $attributes[1]['type']);
$this->assertEquals($emailResponse['body']['status'], $attributes[1]['status']);
$this->assertEquals($emailResponse['body']['required'], $attributes[1]['required']);
$this->assertEquals($emailResponse['body']['array'], $attributes[1]['array']);
$this->assertEquals($emailResponse['body']['default'], $attributes[1]['default']);
$this->assertEquals($emailResponse['body']['format'], $attributes[1]['format']);
$this->assertEquals($ipResponse['body']['key'], $attributes[2]['key']);
$this->assertEquals($ipResponse['body']['type'], $attributes[2]['type']);
$this->assertEquals($ipResponse['body']['status'], $attributes[2]['status']);
$this->assertEquals($ipResponse['body']['required'], $attributes[2]['required']);
$this->assertEquals($ipResponse['body']['array'], $attributes[2]['array']);
$this->assertEquals($ipResponse['body']['default'], $attributes[2]['default']);
$this->assertEquals($ipResponse['body']['format'], $attributes[2]['format']);
$this->assertEquals($urlResponse['body']['key'], $attributes[3]['key']);
$this->assertEquals($urlResponse['body']['type'], $attributes[3]['type']);
$this->assertEquals($urlResponse['body']['status'], $attributes[3]['status']);
$this->assertEquals($urlResponse['body']['required'], $attributes[3]['required']);
$this->assertEquals($urlResponse['body']['array'], $attributes[3]['array']);
$this->assertEquals($urlResponse['body']['default'], $attributes[3]['default']);
$this->assertEquals($urlResponse['body']['format'], $attributes[3]['format']);
$this->assertEquals($integerResponse['body']['key'], $attributes[4]['key']);
$this->assertEquals($integerResponse['body']['type'], $attributes[4]['type']);
$this->assertEquals($integerResponse['body']['status'], $attributes[4]['status']);
$this->assertEquals($integerResponse['body']['required'], $attributes[4]['required']);
$this->assertEquals($integerResponse['body']['array'], $attributes[4]['array']);
$this->assertEquals($integerResponse['body']['default'], $attributes[4]['default']);
$this->assertEquals($integerResponse['body']['min'], $attributes[4]['min']);
$this->assertEquals($integerResponse['body']['max'], $attributes[4]['max']);
$this->assertEquals($floatResponse['body']['key'], $attributes[5]['key']);
$this->assertEquals($floatResponse['body']['type'], $attributes[5]['type']);
$this->assertEquals($floatResponse['body']['status'], $attributes[5]['status']);
$this->assertEquals($floatResponse['body']['required'], $attributes[5]['required']);
$this->assertEquals($floatResponse['body']['array'], $attributes[5]['array']);
$this->assertEquals($floatResponse['body']['default'], $attributes[5]['default']);
$this->assertEquals($floatResponse['body']['min'], $attributes[5]['min']);
$this->assertEquals($floatResponse['body']['max'], $attributes[5]['max']);
$this->assertEquals($booleanResponse['body']['key'], $attributes[6]['key']);
$this->assertEquals($booleanResponse['body']['type'], $attributes[6]['type']);
$this->assertEquals($booleanResponse['body']['status'], $attributes[6]['status']);
$this->assertEquals($booleanResponse['body']['required'], $attributes[6]['required']);
$this->assertEquals($booleanResponse['body']['array'], $attributes[6]['array']);
$this->assertEquals($booleanResponse['body']['default'], $attributes[6]['default']);
$collection = $this->client->call(Client::METHOD_GET, '/database/collections/' . $collectionId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]));
$this->assertEquals(200, $collection['headers']['status-code']);
$attributes = $collection['body']['attributes'];
$this->assertIsArray($attributes);
$this->assertCount(7, $attributes);
$this->assertEquals($stringResponse['body']['key'], $attributes[0]['key']);
$this->assertEquals($stringResponse['body']['type'], $attributes[0]['type']);
$this->assertEquals($stringResponse['body']['status'], $attributes[0]['status']);
$this->assertEquals($stringResponse['body']['required'], $attributes[0]['required']);
$this->assertEquals($stringResponse['body']['array'], $attributes[0]['array']);
$this->assertEquals($stringResponse['body']['size'], $attributes[0]['size']);
$this->assertEquals($stringResponse['body']['default'], $attributes[0]['default']);
$this->assertEquals($emailResponse['body']['key'], $attributes[1]['key']);
$this->assertEquals($emailResponse['body']['type'], $attributes[1]['type']);
$this->assertEquals($emailResponse['body']['status'], $attributes[1]['status']);
$this->assertEquals($emailResponse['body']['required'], $attributes[1]['required']);
$this->assertEquals($emailResponse['body']['array'], $attributes[1]['array']);
$this->assertEquals($emailResponse['body']['default'], $attributes[1]['default']);
$this->assertEquals($emailResponse['body']['format'], $attributes[1]['format']);
$this->assertEquals($ipResponse['body']['key'], $attributes[2]['key']);
$this->assertEquals($ipResponse['body']['type'], $attributes[2]['type']);
$this->assertEquals($ipResponse['body']['status'], $attributes[2]['status']);
$this->assertEquals($ipResponse['body']['required'], $attributes[2]['required']);
$this->assertEquals($ipResponse['body']['array'], $attributes[2]['array']);
$this->assertEquals($ipResponse['body']['default'], $attributes[2]['default']);
$this->assertEquals($ipResponse['body']['format'], $attributes[2]['format']);
$this->assertEquals($urlResponse['body']['key'], $attributes[3]['key']);
$this->assertEquals($urlResponse['body']['type'], $attributes[3]['type']);
$this->assertEquals($urlResponse['body']['status'], $attributes[3]['status']);
$this->assertEquals($urlResponse['body']['required'], $attributes[3]['required']);
$this->assertEquals($urlResponse['body']['array'], $attributes[3]['array']);
$this->assertEquals($urlResponse['body']['default'], $attributes[3]['default']);
$this->assertEquals($urlResponse['body']['format'], $attributes[3]['format']);
$this->assertEquals($integerResponse['body']['key'], $attributes[4]['key']);
$this->assertEquals($integerResponse['body']['type'], $attributes[4]['type']);
$this->assertEquals($integerResponse['body']['status'], $attributes[4]['status']);
$this->assertEquals($integerResponse['body']['required'], $attributes[4]['required']);
$this->assertEquals($integerResponse['body']['array'], $attributes[4]['array']);
$this->assertEquals($integerResponse['body']['default'], $attributes[4]['default']);
$this->assertEquals($integerResponse['body']['min'], $attributes[4]['min']);
$this->assertEquals($integerResponse['body']['max'], $attributes[4]['max']);
$this->assertEquals($floatResponse['body']['key'], $attributes[5]['key']);
$this->assertEquals($floatResponse['body']['type'], $attributes[5]['type']);
$this->assertEquals($floatResponse['body']['status'], $attributes[5]['status']);
$this->assertEquals($floatResponse['body']['required'], $attributes[5]['required']);
$this->assertEquals($floatResponse['body']['array'], $attributes[5]['array']);
$this->assertEquals($floatResponse['body']['default'], $attributes[5]['default']);
$this->assertEquals($floatResponse['body']['min'], $attributes[5]['min']);
$this->assertEquals($floatResponse['body']['max'], $attributes[5]['max']);
$this->assertEquals($booleanResponse['body']['key'], $attributes[6]['key']);
$this->assertEquals($booleanResponse['body']['type'], $attributes[6]['type']);
$this->assertEquals($booleanResponse['body']['status'], $attributes[6]['status']);
$this->assertEquals($booleanResponse['body']['required'], $attributes[6]['required']);
$this->assertEquals($booleanResponse['body']['array'], $attributes[6]['array']);
$this->assertEquals($booleanResponse['body']['default'], $attributes[6]['default']);
return $data;
}
@ -122,13 +530,13 @@ trait DatabaseBase
]);
$this->assertEquals($titleIndex['headers']['status-code'], 201);
$this->assertEquals($titleIndex['body']['$id'], 'titleIndex');
$this->assertEquals($titleIndex['body']['key'], 'titleIndex');
$this->assertEquals($titleIndex['body']['type'], 'fulltext');
$this->assertCount(1, $titleIndex['body']['attributes']);
$this->assertEquals($titleIndex['body']['attributes'][0], 'title');
// wait for database worker to create index
sleep(5);
sleep(2);
$movies = $this->client->call(Client::METHOD_GET, '/database/collections/' . $data['moviesId'], array_merge([
'content-type' => 'application/json',
@ -138,7 +546,7 @@ trait DatabaseBase
$this->assertIsArray($movies['body']['indexes']);
$this->assertCount(1, $movies['body']['indexes']);
$this->assertEquals($movies['body']['indexes'][0]['$id'], $titleIndex['body']['$id']);
$this->assertEquals($movies['body']['indexes'][0]['key'], $titleIndex['body']['key']);
return $data;
}
@ -243,8 +651,8 @@ trait DatabaseBase
$this->assertCount(1, $document3['body']['$read']);
$this->assertCount(1, $document3['body']['$write']);
$this->assertCount(2, $document3['body']['actors']);
$this->assertEquals($document2['body']['actors'][0], 'Tom Holland');
$this->assertEquals($document2['body']['actors'][1], 'Zendaya Maree Stoermer');
$this->assertEquals($document3['body']['actors'][0], 'Tom Holland');
$this->assertEquals($document3['body']['actors'][1], 'Zendaya Maree Stoermer');
$this->assertEquals($document4['headers']['status-code'], 400);
@ -264,6 +672,7 @@ trait DatabaseBase
'orderTypes' => ['ASC'],
]);
$this->assertEquals($documents['headers']['status-code'], 200);
$this->assertEquals(1944, $documents['body']['documents'][0]['releaseYear']);
$this->assertEquals(2017, $documents['body']['documents'][1]['releaseYear']);
$this->assertEquals(2019, $documents['body']['documents'][2]['releaseYear']);
@ -277,6 +686,7 @@ trait DatabaseBase
'orderTypes' => ['DESC'],
]);
$this->assertEquals($documents['headers']['status-code'], 200);
$this->assertEquals(1944, $documents['body']['documents'][2]['releaseYear']);
$this->assertEquals(2017, $documents['body']['documents'][1]['releaseYear']);
$this->assertEquals(2019, $documents['body']['documents'][0]['releaseYear']);
@ -298,6 +708,7 @@ trait DatabaseBase
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$this->assertEquals($base['headers']['status-code'], 200);
$this->assertEquals('Captain America', $base['body']['documents'][0]['title']);
$this->assertEquals('Spider-Man: Far From Home', $base['body']['documents'][1]['title']);
$this->assertEquals('Spider-Man: Homecoming', $base['body']['documents'][2]['title']);
@ -310,6 +721,7 @@ trait DatabaseBase
'after' => $base['body']['documents'][0]['$id']
]);
$this->assertEquals($documents['headers']['status-code'], 200);
$this->assertEquals($base['body']['documents'][1]['$id'], $documents['body']['documents'][0]['$id']);
$this->assertEquals($base['body']['documents'][2]['$id'], $documents['body']['documents'][1]['$id']);
$this->assertCount(2, $documents['body']['documents']);
@ -321,6 +733,7 @@ trait DatabaseBase
'after' => $base['body']['documents'][2]['$id']
]);
$this->assertEquals($documents['headers']['status-code'], 200);
$this->assertEmpty($documents['body']['documents']);
/**
@ -334,6 +747,7 @@ trait DatabaseBase
'orderTypes' => ['ASC'],
]);
$this->assertEquals($base['headers']['status-code'], 200);
$this->assertEquals(1944, $base['body']['documents'][0]['releaseYear']);
$this->assertEquals(2017, $base['body']['documents'][1]['releaseYear']);
$this->assertEquals(2019, $base['body']['documents'][2]['releaseYear']);
@ -348,6 +762,7 @@ trait DatabaseBase
'after' => $base['body']['documents'][1]['$id']
]);
$this->assertEquals($documents['headers']['status-code'], 200);
$this->assertEquals($base['body']['documents'][2]['$id'], $documents['body']['documents'][0]['$id']);
$this->assertCount(1, $documents['body']['documents']);
@ -362,6 +777,7 @@ trait DatabaseBase
'orderTypes' => ['DESC'],
]);
$this->assertEquals($base['headers']['status-code'], 200);
$this->assertEquals(1944, $base['body']['documents'][2]['releaseYear']);
$this->assertEquals(2017, $base['body']['documents'][1]['releaseYear']);
$this->assertEquals(2019, $base['body']['documents'][0]['releaseYear']);
@ -376,6 +792,7 @@ trait DatabaseBase
'after' => $base['body']['documents'][1]['$id']
]);
$this->assertEquals($documents['headers']['status-code'], 200);
$this->assertEquals($base['body']['documents'][2]['$id'], $documents['body']['documents'][0]['$id']);
$this->assertCount(1, $documents['body']['documents']);
@ -408,6 +825,7 @@ trait DatabaseBase
'orderTypes' => ['ASC'],
]);
$this->assertEquals($documents['headers']['status-code'], 200);
$this->assertEquals(1944, $documents['body']['documents'][0]['releaseYear']);
$this->assertCount(1, $documents['body']['documents']);
@ -421,6 +839,7 @@ trait DatabaseBase
'orderTypes' => ['ASC'],
]);
$this->assertEquals($documents['headers']['status-code'], 200);
$this->assertEquals(2017, $documents['body']['documents'][0]['releaseYear']);
$this->assertEquals(2019, $documents['body']['documents'][1]['releaseYear']);
$this->assertCount(2, $documents['body']['documents']);
@ -431,41 +850,46 @@ trait DatabaseBase
/**
* @depends testCreateDocument
*/
public function testDocumentsListSuccessSearch(array $data):array
{
$documents = $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()), [
'queries' => ['title.search("Captain America")'],
]);
// public function testDocumentsListSuccessSearch(array $data):array
// {
// $documents = $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()), [
// 'queries' => ['title.search("Captain America")'],
// ]);
$this->assertEquals(1944, $documents['body']['documents'][0]['releaseYear']);
$this->assertCount(1, $documents['body']['documents']);
// var_dump($documents);
$documents = $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()), [
'queries' => ['title.search("Homecoming")'],
]);
// $this->assertEquals($documents['headers']['status-code'], 200);
// $this->assertEquals(1944, $documents['body']['documents'][0]['releaseYear']);
// $this->assertCount(1, $documents['body']['documents']);
$this->assertEquals(2017, $documents['body']['documents'][0]['releaseYear']);
$this->assertCount(1, $documents['body']['documents']);
// $documents = $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()), [
// 'queries' => ['title.search("Homecoming")'],
// ]);
$documents = $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()), [
'queries' => ['title.search("spider")'],
]);
// $this->assertEquals($documents['headers']['status-code'], 200);
// $this->assertEquals(2017, $documents['body']['documents'][0]['releaseYear']);
// $this->assertCount(1, $documents['body']['documents']);
$this->assertEquals(2019, $documents['body']['documents'][0]['releaseYear']);
$this->assertEquals(2017, $documents['body']['documents'][1]['releaseYear']);
$this->assertCount(2, $documents['body']['documents']);
// $documents = $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()), [
// 'queries' => ['title.search("spider")'],
// ]);
return [];
}
// $this->assertEquals($documents['headers']['status-code'], 200);
// $this->assertEquals(2019, $documents['body']['documents'][0]['releaseYear']);
// $this->assertEquals(2017, $documents['body']['documents'][1]['releaseYear']);
// $this->assertCount(2, $documents['body']['documents']);
// return [];
// }
// TODO@kodumbeats test for empty searches and misformatted queries
/**
@ -626,6 +1050,7 @@ trait DatabaseBase
'name' => 'invalidDocumentStructure',
'read' => ['role:all'],
'write' => ['role:all'],
'permission' => 'document',
]);
$this->assertEquals(201, $collection['headers']['status-code']);
@ -742,7 +1167,7 @@ trait DatabaseBase
// $this->assertEquals('Minimum value must be lesser than maximum value', $invalidRange['body']['message']);
// wait for worker to add attributes
sleep(15);
sleep(2);
$collection = $this->client->call(Client::METHOD_GET, '/database/collections/' . $collectionId, array_merge([
'content-type' => 'application/json',
@ -751,7 +1176,6 @@ trait DatabaseBase
]), []);
$this->assertCount(7, $collection['body']['attributes']);
// $this->assertCount(0, $collection['body']['attributesInQueue']);
/**
* Test for successful validation
@ -937,7 +1361,6 @@ trait DatabaseBase
'write' => ['user:'.$this->getUser()['$id']],
]);
$this->assertEquals(400, $badEmail['headers']['status-code']);
$this->assertEquals(400, $badIp['headers']['status-code']);
$this->assertEquals(400, $badUrl['headers']['status-code']);
@ -950,8 +1373,8 @@ trait DatabaseBase
$this->assertEquals('Invalid document structure: Attribute "url" has invalid format. Value must be a valid URL', $badUrl['body']['message']);
$this->assertEquals('Invalid document structure: Attribute "range" has invalid format. Value must be a valid range between 1 and 10', $badRange['body']['message']);
$this->assertEquals('Invalid document structure: Attribute "floatRange" has invalid format. Value must be a valid range between 1 and 1', $badFloatRange['body']['message']);
$this->assertEquals('Invalid document structure: Attribute "upperBound" has invalid format. Value must be a valid range between inf and 10', $tooHigh['body']['message']);
$this->assertEquals('Invalid document structure: Attribute "lowerBound" has invalid format. Value must be a valid range between 5 and inf', $tooLow['body']['message']);
$this->assertEquals('Invalid document structure: Attribute "upperBound" has invalid format. Value must be a valid range between -9,223,372,036,854,775,808 and 10', $tooHigh['body']['message']);
$this->assertEquals('Invalid document structure: Attribute "lowerBound" has invalid format. Value must be a valid range between 5 and 9,223,372,036,854,775,808', $tooLow['body']['message']);
}
/**
@ -1079,4 +1502,86 @@ trait DatabaseBase
return $data;
}
/**
* @depends testDefaultPermissions
*/
public function testUniqueIndexDuplicate(array $data): array
{
$uniqueIndex = $this->client->call(Client::METHOD_POST, '/database/collections/' . $data['moviesId'] . '/indexes', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'indexId' => 'unique_title',
'type' => 'unique',
'attributes' => ['title'],
]);
$this->assertEquals($uniqueIndex['headers']['status-code'], 201);
sleep(2);
// test for failure
$duplicate = $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',
'releaseYear' => 1944,
'actors' => [
'Chris Evans',
'Samuel Jackson',
]
],
'read' => ['user:'.$this->getUser()['$id']],
'write' => ['user:'.$this->getUser()['$id']],
]);
$this->assertEquals(409, $duplicate['headers']['status-code']);
// Test for exception when updating document to conflict
$document = $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 5',
'releaseYear' => 1944,
'actors' => [
'Chris Evans',
'Samuel Jackson',
]
],
'read' => ['user:'.$this->getUser()['$id']],
'write' => ['user:'.$this->getUser()['$id']],
]);
$this->assertEquals(201, $document['headers']['status-code']);
// Test for exception when updating document to conflict
$duplicate = $this->client->call(Client::METHOD_PATCH, '/database/collections/' . $data['moviesId'] . '/documents/' . $document['body']['$id'], array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'documentId' => 'unique()',
'data' => [
'title' => 'Captain America',
'releaseYear' => 1944,
'actors' => [
'Chris Evans',
'Samuel Jackson',
]
],
'read' => ['user:'.$this->getUser()['$id']],
'write' => ['user:'.$this->getUser()['$id']],
]);
$this->assertEquals(409, $duplicate['headers']['status-code']);
return $data;
}
}

View file

@ -0,0 +1,125 @@
<?php
namespace Tests\E2E\Services\Database;
use Tests\E2E\Scopes\Scope;
use Tests\E2E\Scopes\ProjectCustom;
use Tests\E2E\Client;
use Tests\E2E\Scopes\SideConsole;
class DatabaseConsoleClientTest extends Scope
{
use ProjectCustom;
use SideConsole;
public function testCreateCollection():array
{
/**
* Test for SUCCESS
*/
$movies = $this->client->call(Client::METHOD_POST, '/database/collections', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'collectionId' => 'unique()',
'name' => 'Movies',
'read' => ['role:all'],
'write' => ['role:all'],
'permission' => 'document',
]);
$this->assertEquals($movies['headers']['status-code'], 201);
$this->assertEquals($movies['body']['name'], 'Movies');
return ['moviesId' => $movies['body']['$id']];
}
public function testGetDatabaseUsage()
{
/**
* Test for FAILURE
*/
$response = $this->client->call(Client::METHOD_GET, '/database/usage', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id']
], $this->getHeaders()), [
'range' => '32h'
]);
$this->assertEquals($response['headers']['status-code'], 400);
/**
* Test for SUCCESS
*/
$response = $this->client->call(Client::METHOD_GET, '/database/usage', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id']
], $this->getHeaders()), [
'range' => '24h'
]);
$this->assertEquals($response['headers']['status-code'], 200);
$this->assertEquals(count($response['body']), 11);
$this->assertEquals($response['body']['range'], '24h');
$this->assertIsArray($response['body']['documents.count']);
$this->assertIsArray($response['body']['collections.count']);
$this->assertIsArray($response['body']['documents.create']);
$this->assertIsArray($response['body']['documents.read']);
$this->assertIsArray($response['body']['documents.update']);
$this->assertIsArray($response['body']['documents.delete']);
$this->assertIsArray($response['body']['collections.create']);
$this->assertIsArray($response['body']['collections.read']);
$this->assertIsArray($response['body']['collections.update']);
$this->assertIsArray($response['body']['collections.delete']);
}
/**
* @depends testCreateCollection
*/
public function testGetCollectionUsage(array $data)
{
/**
* Test for FAILURE
*/
$response = $this->client->call(Client::METHOD_GET, '/database/'.$data['moviesId'].'/usage', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id']
], $this->getHeaders()), [
'range' => '32h'
]);
$this->assertEquals($response['headers']['status-code'], 400);
$response = $this->client->call(Client::METHOD_GET, '/database/randomCollectionId/usage', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id']
], $this->getHeaders()), [
'range' => '24h'
]);
$this->assertEquals($response['headers']['status-code'], 404);
/**
* Test for SUCCESS
*/
$response = $this->client->call(Client::METHOD_GET, '/database/'.$data['moviesId'].'/usage', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id']
], $this->getHeaders()), [
'range' => '24h'
]);
$this->assertEquals($response['headers']['status-code'], 200);
$this->assertEquals(count($response['body']), 6);
$this->assertEquals($response['body']['range'], '24h');
$this->assertIsArray($response['body']['documents.count']);
$this->assertIsArray($response['body']['documents.create']);
$this->assertIsArray($response['body']['documents.read']);
$this->assertIsArray($response['body']['documents.update']);
$this->assertIsArray($response['body']['documents.delete']);
}
}

Some files were not shown because too many files have changed in this diff Show more