Merge branch 'feat-database-indexing' of https://github.com/appwrite/appwrite into feat-db-project-subqueries
This commit is contained in:
commit
97aa1908c8
10
.travis.yml
10
.travis.yml
|
@ -6,6 +6,10 @@ arch:
|
|||
|
||||
os: linux
|
||||
|
||||
# Small change
|
||||
vm:
|
||||
size: large
|
||||
|
||||
language: shell
|
||||
|
||||
notifications:
|
||||
|
@ -35,6 +39,12 @@ install:
|
|||
|
||||
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
|
||||
|
|
|
@ -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 && \
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -174,7 +174,7 @@ $collections = [
|
|||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
'filters' => ['casting'],
|
||||
],
|
||||
[
|
||||
'$id' => 'signed',
|
||||
|
@ -214,7 +214,7 @@ $collections = [
|
|||
'required' => false,
|
||||
'default' => new stdClass,
|
||||
'array' => false,
|
||||
'filters' => ['json'],
|
||||
'filters' => ['json', 'range'],
|
||||
],
|
||||
[
|
||||
'$id' => 'filters',
|
||||
|
@ -2082,6 +2082,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;
|
||||
|
|
|
@ -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
|
@ -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' => ''
|
||||
]
|
||||
],
|
||||
],
|
||||
|
|
|
@ -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()) {
|
||||
|
@ -117,9 +119,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);
|
||||
});
|
||||
|
@ -147,13 +152,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();
|
||||
|
@ -164,7 +171,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
|
||||
|
@ -205,7 +212,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')) {
|
||||
|
@ -227,6 +234,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);
|
||||
});
|
||||
|
||||
|
@ -357,7 +369,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 */
|
||||
|
@ -365,6 +378,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();
|
||||
|
@ -539,12 +553,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)]))
|
||||
|
@ -595,7 +614,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 */
|
||||
|
@ -604,6 +624,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();
|
||||
|
||||
|
@ -683,7 +704,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')) {
|
||||
|
@ -771,10 +797,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);
|
||||
});
|
||||
|
||||
|
@ -791,12 +822,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);
|
||||
});
|
||||
|
||||
|
@ -814,10 +850,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);
|
||||
|
@ -831,6 +869,9 @@ App::get('/v1/account/sessions')
|
|||
$sessions[$key] = $session;
|
||||
}
|
||||
|
||||
$usage
|
||||
->setParam('users.read', 1)
|
||||
;
|
||||
$response->dynamic(new Document([
|
||||
'sessions' => $sessions,
|
||||
'sum' => count($sessions),
|
||||
|
@ -853,13 +894,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);
|
||||
|
||||
|
@ -906,6 +949,9 @@ App::get('/v1/account/logs')
|
|||
|
||||
}
|
||||
|
||||
$usage
|
||||
->setParam('users.read', 1)
|
||||
;
|
||||
$response->dynamic(new Document(['logs' => $output]), Response::MODEL_LOG_LIST);
|
||||
});
|
||||
|
||||
|
@ -925,11 +971,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')
|
||||
|
@ -948,6 +996,10 @@ App::get('/v1/account/sessions/:sessionId')
|
|||
->setAttribute('countryName', $countryName)
|
||||
;
|
||||
|
||||
$usage
|
||||
->setParam('users.read', 1)
|
||||
;
|
||||
|
||||
return $response->dynamic($session, Response::MODEL_SESSION);
|
||||
}
|
||||
}
|
||||
|
@ -972,18 +1024,24 @@ 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));
|
||||
|
||||
$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);
|
||||
|
@ -1007,11 +1065,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
|
||||
|
@ -1026,9 +1086,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);
|
||||
});
|
||||
|
||||
|
@ -1050,11 +1113,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
|
||||
|
||||
|
@ -1066,24 +1131,25 @@ 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
|
||||
|
||||
if ($profile) {
|
||||
throw new Exception('User already registered', 400);
|
||||
}
|
||||
|
||||
$user = $dbForInternal->updateDocument('users', $user->getId(), $user
|
||||
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
|
||||
);
|
||||
);
|
||||
} 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);
|
||||
});
|
||||
|
||||
|
@ -1104,19 +1170,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);
|
||||
});
|
||||
|
||||
|
@ -1137,13 +1208,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));
|
||||
|
@ -1159,7 +1232,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())
|
||||
;
|
||||
|
||||
|
@ -1173,6 +1246,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'))
|
||||
|
@ -1200,7 +1276,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 */
|
||||
|
@ -1208,6 +1285,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')
|
||||
|
@ -1225,7 +1303,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);
|
||||
|
@ -1254,6 +1332,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();
|
||||
}
|
||||
}
|
||||
|
@ -1280,7 +1362,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 */
|
||||
|
@ -1288,6 +1371,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', []);
|
||||
|
@ -1298,7 +1382,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')) {
|
||||
|
@ -1323,13 +1407,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();
|
||||
});
|
||||
|
||||
|
@ -1357,7 +1447,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 */
|
||||
|
@ -1366,6 +1457,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);
|
||||
|
@ -1430,9 +1522,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);
|
||||
});
|
||||
|
@ -1458,10 +1553,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);
|
||||
|
@ -1505,9 +1602,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);
|
||||
});
|
||||
|
||||
|
@ -1535,7 +1635,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 */
|
||||
|
@ -1545,6 +1646,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);
|
||||
|
@ -1599,9 +1701,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);
|
||||
});
|
||||
|
@ -1626,11 +1731,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);
|
||||
|
||||
|
@ -1665,8 +1772,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
|
@ -146,6 +146,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')
|
||||
|
@ -164,99 +167,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')
|
||||
|
|
|
@ -6,11 +6,15 @@ 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\CLI\CLI;
|
||||
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;
|
||||
|
@ -20,13 +24,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');
|
||||
|
@ -70,7 +72,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) {
|
||||
|
@ -217,22 +219,25 @@ App::get('/v1/projects/:projectId')
|
|||
});
|
||||
|
||||
App::get('/v1/projects/:projectId/usage')
|
||||
->desc('Get Project Usage')
|
||||
->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);
|
||||
|
@ -241,172 +246,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')
|
||||
|
@ -469,7 +374,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')
|
||||
|
|
|
@ -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')
|
||||
|
@ -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');
|
||||
|
||||
|
@ -145,11 +147,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);
|
||||
|
@ -175,9 +179,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 */
|
||||
|
||||
$queries = ($search) ? [new Query('name', Query::TYPE_SEARCH, $search)] : [];
|
||||
|
||||
|
@ -189,6 +195,11 @@ App::get('/v1/storage/files')
|
|||
}
|
||||
}
|
||||
|
||||
$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),
|
||||
|
@ -209,16 +220,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);
|
||||
});
|
||||
|
||||
|
@ -249,11 +265,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';
|
||||
|
||||
|
@ -366,6 +384,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)
|
||||
|
@ -390,9 +413,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);
|
||||
|
||||
|
@ -424,6 +449,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'))
|
||||
|
@ -448,9 +478,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');
|
||||
|
@ -490,6 +522,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)
|
||||
|
@ -520,7 +557,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 */
|
||||
|
@ -539,7 +577,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);
|
||||
|
@ -567,7 +610,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);
|
||||
|
||||
|
@ -585,11 +628,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
|
||||
|
@ -597,4 +642,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);
|
||||
});
|
|
@ -399,7 +399,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);
|
||||
|
@ -561,7 +561,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);
|
||||
|
@ -686,7 +686,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')) {
|
||||
|
@ -779,7 +779,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
|
||||
|
|
|
@ -17,6 +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')
|
||||
|
@ -36,9 +40,11 @@ App::post('/v1/users')
|
|||
->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);
|
||||
|
||||
|
@ -65,6 +71,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);
|
||||
});
|
||||
|
@ -87,9 +97,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);
|
||||
|
@ -101,6 +113,10 @@ App::get('/v1/users')
|
|||
|
||||
$results = $dbForInternal->find('users', [], $limit, $offset, [], [$orderType], $afterUser ?? null);
|
||||
$sum = $dbForInternal->count('users', [], APP_LIMIT_COUNT);
|
||||
|
||||
$usage
|
||||
->setParam('users.read', 1)
|
||||
;
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'users' => $results,
|
||||
|
@ -122,9 +138,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);
|
||||
|
||||
|
@ -132,6 +150,9 @@ App::get('/v1/users/:userId')
|
|||
throw new Exception('User not found', 404);
|
||||
}
|
||||
|
||||
$usage
|
||||
->setParam('users.read', 1)
|
||||
;
|
||||
$response->dynamic($user, Response::MODEL_USER);
|
||||
});
|
||||
|
||||
|
@ -149,9 +170,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);
|
||||
|
||||
|
@ -161,6 +184,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);
|
||||
});
|
||||
|
||||
|
@ -179,10 +205,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);
|
||||
|
||||
|
@ -202,6 +230,9 @@ App::get('/v1/users/:userId/sessions')
|
|||
$sessions[$key] = $session;
|
||||
}
|
||||
|
||||
$usage
|
||||
->setParam('users.read', 1)
|
||||
;
|
||||
$response->dynamic(new Document([
|
||||
'sessions' => $sessions,
|
||||
'sum' => count($sessions),
|
||||
|
@ -224,12 +255,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);
|
||||
|
||||
|
@ -311,6 +344,9 @@ App::get('/v1/users/:userId/logs')
|
|||
}
|
||||
}
|
||||
|
||||
$usage
|
||||
->setParam('users.read', 1)
|
||||
;
|
||||
$response->dynamic(new Document(['logs' => $output]), Response::MODEL_LOG_LIST);
|
||||
});
|
||||
|
||||
|
@ -330,9 +366,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);
|
||||
|
||||
|
@ -342,6 +380,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);
|
||||
});
|
||||
|
||||
|
@ -361,9 +402,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);
|
||||
|
||||
|
@ -373,6 +416,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);
|
||||
});
|
||||
|
||||
|
@ -392,9 +561,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);
|
||||
|
||||
|
@ -404,6 +575,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);
|
||||
});
|
||||
|
||||
|
@ -423,10 +597,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);
|
||||
|
||||
|
@ -453,6 +629,11 @@ App::delete('/v1/users/:userId/sessions/:sessionId')
|
|||
}
|
||||
}
|
||||
|
||||
$usage
|
||||
->setParam('users.update', 1)
|
||||
->setParam('users.sessions.delete', 1)
|
||||
;
|
||||
|
||||
$response->noContent();
|
||||
});
|
||||
|
||||
|
@ -471,10 +652,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);
|
||||
|
||||
|
@ -494,6 +677,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();
|
||||
});
|
||||
|
||||
|
@ -513,11 +700,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);
|
||||
|
||||
|
@ -528,11 +717,6 @@ App::delete('/v1/users/:userId')
|
|||
if (!$dbForInternal->deleteDocument('users', $userId)) {
|
||||
throw new Exception('Failed to remove user from DB', 500);
|
||||
}
|
||||
|
||||
// $dbForInternal->createDocument('users', new Document([
|
||||
// '$id' => $userId,
|
||||
// '$read' => ['role:all'],
|
||||
// ]));
|
||||
|
||||
$deletes
|
||||
->setParam('type', DELETE_TYPE_DOCUMENT)
|
||||
|
@ -543,5 +727,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);
|
||||
});
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
91
app/init.php
91
app/init.php
|
@ -17,6 +17,7 @@ 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;
|
||||
|
@ -63,6 +64,11 @@ const APP_LIMIT_COUNT = 5000;
|
|||
const APP_LIMIT_USERS = 10000;
|
||||
const APP_CACHE_BUSTER = 151;
|
||||
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';
|
||||
|
@ -89,6 +95,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';
|
||||
|
@ -141,9 +148,7 @@ if(!empty($user) || !empty($pass)) {
|
|||
}
|
||||
|
||||
/**
|
||||
* DB Filters
|
||||
*
|
||||
* Make sure the value of an attribute that uses sub-query filters is set to 'null' otherwise the filters might not work properly
|
||||
* Old DB Filters
|
||||
*/
|
||||
DatabaseOld::addFilter('json',
|
||||
function($value) {
|
||||
|
@ -179,6 +184,43 @@ DatabaseOld::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;
|
||||
|
@ -187,7 +229,7 @@ Database::addFilter('subQueryAttributes',
|
|||
return $database
|
||||
->find('attributes', [
|
||||
new Query('collectionId', Query::TYPE_EQUAL, [$document->getId()])
|
||||
], 1017, 0, []);
|
||||
], $database->getAttributeLimit(), 0, []);
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -211,7 +253,7 @@ Database::addFilter('subQueryPlatforms',
|
|||
return $database
|
||||
->find('platforms', [
|
||||
new Query('projectId', Query::TYPE_EQUAL, [$document->getId()])
|
||||
], 5000, 0, []);
|
||||
], $database->getIndexLimit(), 0, []);
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -223,7 +265,7 @@ Database::addFilter('subQueryDomains',
|
|||
return $database
|
||||
->find('domains', [
|
||||
new Query('projectId', Query::TYPE_EQUAL, [$document->getId()])
|
||||
], 5000, 0, []);
|
||||
], $database->getIndexLimit(), 0, []);
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -235,7 +277,7 @@ Database::addFilter('subQueryKeys',
|
|||
return $database
|
||||
->find('keys', [
|
||||
new Query('projectId', Query::TYPE_EQUAL, [$document->getId()])
|
||||
], 5000, 0, []);
|
||||
], $database->getIndexLimit(), 0, []);
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -247,7 +289,7 @@ Database::addFilter('subQueryWebhooks',
|
|||
return $database
|
||||
->find('webhooks', [
|
||||
new Query('projectId', Query::TYPE_EQUAL, [$document->getId()])
|
||||
], 5000, 0, []);
|
||||
], $database->getIndexLimit(), 0, []);
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -275,25 +317,25 @@ Database::addFilter('encrypt',
|
|||
/**
|
||||
* DB Formats
|
||||
*/
|
||||
Structure::addFormat('email', function() {
|
||||
Structure::addFormat(APP_DATABASE_ATTRIBUTE_EMAIL, function() {
|
||||
return new Email();
|
||||
}, Database::VAR_STRING);
|
||||
|
||||
Structure::addFormat('ip', function() {
|
||||
Structure::addFormat(APP_DATABASE_ATTRIBUTE_IP, function() {
|
||||
return new IP();
|
||||
}, Database::VAR_STRING);
|
||||
|
||||
Structure::addFormat('url', function() {
|
||||
Structure::addFormat(APP_DATABASE_ATTRIBUTE_URL, function() {
|
||||
return new URL();
|
||||
}, Database::VAR_STRING);
|
||||
|
||||
Structure::addFormat('int-range', function($attribute) {
|
||||
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) {
|
||||
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);
|
||||
|
@ -396,6 +438,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
|
||||
|
|
|
@ -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
586
app/tasks/usage.php
Normal 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);
|
||||
});
|
|
@ -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> <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> <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> <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>
|
||||
|
||||
|
|
|
@ -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"; ?>
|
||||
|
|
|
@ -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;
|
||||
});
|
||||
|
|
@ -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');
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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");
|
||||
|
@ -94,7 +94,7 @@ class DatabaseV1 extends Worker
|
|||
$dbForInternal->updateDocument('attributes', $attribute->getId(), $attribute->setAttribute('status', 'failed'));
|
||||
}
|
||||
|
||||
$dbForInternal->purgeDocument('collections', $collectionId);
|
||||
$dbForInternal->deleteCachedDocument('collections', $collectionId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -120,7 +120,58 @@ class DatabaseV1 extends Worker
|
|||
$dbForInternal->updateDocument('attributes', $attribute->getId(), $attribute->setAttribute('status', 'failed'));
|
||||
}
|
||||
|
||||
$dbForInternal->purgeDocument('collections', $collectionId);
|
||||
// The underlying database removes/rebuilds indexes when attribute is removed
|
||||
// Update indexes table with changes
|
||||
/** @var Document[] $indexes */
|
||||
$indexes = $collection->getAttribute('indexes', []);
|
||||
|
||||
foreach ($indexes as $index) {
|
||||
/** @var string[] $attributes */
|
||||
$attributes = $index->getAttribute('attributes');
|
||||
$lengths = $index->getAttribute('lengths');
|
||||
$orders = $index->getAttribute('orders');
|
||||
|
||||
$found = \array_search($key, $attributes);
|
||||
|
||||
if ($found !== false) {
|
||||
// If found, remove entry from attributes, lengths, and orders
|
||||
// array_values wraps array_diff to reindex array keys
|
||||
// when found attribute is removed from array
|
||||
$attributes = \array_values(\array_diff($attributes, [$attributes[$found]]));
|
||||
$lengths = \array_values(\array_diff($lengths, [$lengths[$found]]));
|
||||
$orders = \array_values(\array_diff($orders, [$orders[$found]]));
|
||||
|
||||
if (empty($attributes)) {
|
||||
$dbForInternal->deleteDocument('indexes', $index->getId());
|
||||
} else {
|
||||
$index
|
||||
->setAttribute('attributes', $attributes, Document::SET_TYPE_ASSIGN)
|
||||
->setAttribute('lengths', $lengths, Document::SET_TYPE_ASSIGN)
|
||||
->setAttribute('orders', $orders, Document::SET_TYPE_ASSIGN)
|
||||
;
|
||||
|
||||
// Check if an index exists with the same attributes and orders
|
||||
$exists = false;
|
||||
foreach ($indexes as $existing) {
|
||||
if ($existing->getAttribute('key') !== $index->getAttribute('key') // Ignore itself
|
||||
&& $existing->getAttribute('attributes') === $index->getAttribute('attributes')
|
||||
&& $existing->getAttribute('orders') === $index->getAttribute('orders')
|
||||
) {
|
||||
$exists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($exists) { // Delete the duplicate if created, else update in db
|
||||
$this->deleteIndex($collection, $index, $projectId);
|
||||
} else {
|
||||
$dbForInternal->updateDocument('indexes', $index->getId(), $index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$dbForInternal->deleteCachedDocument('collections', $collectionId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -150,7 +201,7 @@ class DatabaseV1 extends Worker
|
|||
$dbForInternal->updateDocument('indexes', $index->getId(), $index->setAttribute('status', 'failed'));
|
||||
}
|
||||
|
||||
$dbForInternal->purgeDocument('collections', $collectionId);
|
||||
$dbForInternal->deleteCachedDocument('collections', $collectionId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -168,7 +219,7 @@ class DatabaseV1 extends Worker
|
|||
|
||||
try {
|
||||
if(!$dbForExternal->deleteIndex($collectionId, $key)) {
|
||||
throw new Exception('Failed to delete Attribute');
|
||||
throw new Exception('Failed to delete index');
|
||||
}
|
||||
|
||||
$dbForInternal->deleteDocument('indexes', $index->getId());
|
||||
|
@ -177,6 +228,6 @@ class DatabaseV1 extends Worker
|
|||
$dbForInternal->updateDocument('indexes', $index->getId(), $index->setAttribute('status', 'failed'));
|
||||
}
|
||||
|
||||
$dbForInternal->purgeDocument('collections', $collectionId);
|
||||
$dbForInternal->deleteCachedDocument('collections', $collectionId);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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
3
bin/usage
Executable file
|
@ -0,0 +1,3 @@
|
|||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/cli.php usage $@
|
|
@ -45,7 +45,7 @@
|
|||
"utopia-php/cache": "0.4.*",
|
||||
"utopia-php/cli": "0.11.*",
|
||||
"utopia-php/config": "0.2.*",
|
||||
"utopia-php/database": "dev-feat-adjusted-query-validator as 0.10.0",
|
||||
"utopia-php/database": "0.10.0",
|
||||
"utopia-php/locale": "0.4.*",
|
||||
"utopia-php/registry": "0.5.*",
|
||||
"utopia-php/preloader": "0.2.*",
|
||||
|
@ -63,12 +63,7 @@
|
|||
"adhocore/jwt": "1.1.2",
|
||||
"slickdeals/statsd": "3.1.0"
|
||||
},
|
||||
"repositories": [
|
||||
{
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/database"
|
||||
}
|
||||
],
|
||||
"repositories": [],
|
||||
"require-dev": {
|
||||
"appwrite/sdk-generator": "0.13.0",
|
||||
"swoole/ide-helper": "4.6.7",
|
||||
|
|
170
composer.lock
generated
170
composer.lock
generated
|
@ -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": "175f077512c575216c4c88f1d33c6d00",
|
||||
"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",
|
||||
|
@ -1984,11 +1984,17 @@
|
|||
},
|
||||
{
|
||||
"name": "utopia-php/database",
|
||||
"version": "dev-feat-adjusted-query-validator",
|
||||
"version": "0.10.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/database",
|
||||
"reference": "cb73391371f70ddb54bc0000064b15c5f173cb7a"
|
||||
"url": "https://github.com/utopia-php/database.git",
|
||||
"reference": "b7c60b0ec769a9050dd2b939b78ff1f5d4fa27e8"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/database/zipball/b7c60b0ec769a9050dd2b939b78ff1f5d4fa27e8",
|
||||
"reference": "b7c60b0ec769a9050dd2b939b78ff1f5d4fa27e8",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-mongodb": "*",
|
||||
|
@ -2011,11 +2017,7 @@
|
|||
"Utopia\\Database\\": "src/Database"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Utopia\\Tests\\": "tests/Database"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
|
@ -2037,7 +2039,11 @@
|
|||
"upf",
|
||||
"utopia"
|
||||
],
|
||||
"time": "2021-08-23T14:18:47+00:00"
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/database/issues",
|
||||
"source": "https://github.com/utopia-php/database/tree/0.10.0"
|
||||
},
|
||||
"time": "2021-10-04T17:23:25+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/domains",
|
||||
|
@ -2576,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": {
|
||||
|
@ -2653,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": [
|
||||
{
|
||||
|
@ -2661,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",
|
||||
|
@ -3383,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": {
|
||||
|
@ -3433,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",
|
||||
|
@ -3712,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": {
|
||||
|
@ -3729,7 +3735,8 @@
|
|||
"phpdocumentor/reflection-common": "^2.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"ext-tokenizer": "*"
|
||||
"ext-tokenizer": "*",
|
||||
"psalm/phar": "^4.8"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
|
@ -3755,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": {
|
||||
|
@ -3822,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",
|
||||
|
@ -3893,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": [
|
||||
{
|
||||
|
@ -3901,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",
|
||||
|
@ -5313,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": {
|
||||
|
@ -5392,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": [
|
||||
{
|
||||
|
@ -5408,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",
|
||||
|
@ -5882,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": {
|
||||
|
@ -5945,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": [
|
||||
{
|
||||
|
@ -5961,7 +5968,7 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2021-06-27T11:44:38+00:00"
|
||||
"time": "2021-08-26T08:00:08+00:00"
|
||||
},
|
||||
{
|
||||
"name": "theseer/tokenizer",
|
||||
|
@ -6015,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": {
|
||||
|
@ -6034,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": {
|
||||
|
@ -6078,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": [
|
||||
{
|
||||
|
@ -6090,7 +6097,7 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2021-05-16T12:12:47+00:00"
|
||||
"time": "2021-09-17T08:39:54+00:00"
|
||||
},
|
||||
{
|
||||
"name": "vimeo/psalm",
|
||||
|
@ -6248,18 +6255,9 @@
|
|||
"time": "2015-12-17T08:42:14+00:00"
|
||||
}
|
||||
],
|
||||
"aliases": [
|
||||
{
|
||||
"package": "utopia-php/database",
|
||||
"version": "dev-feat-adjusted-query-validator",
|
||||
"alias": "0.10.0",
|
||||
"alias_normalized": "0.10.0.0"
|
||||
}
|
||||
],
|
||||
"aliases": [],
|
||||
"minimum-stability": "stable",
|
||||
"stability-flags": {
|
||||
"utopia-php/database": 20
|
||||
},
|
||||
"stability-flags": [],
|
||||
"prefer-stable": false,
|
||||
"prefer-lowest": false,
|
||||
"platform": {
|
||||
|
|
|
@ -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
|
||||
|
@ -349,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
|
||||
|
|
13
public/dist/scripts/app-all.js
vendored
13
public/dist/scripts/app-all.js
vendored
|
@ -487,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;}
|
||||
|
|
13
public/dist/scripts/app-dep.js
vendored
13
public/dist/scripts/app-dep.js
vendored
|
@ -487,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;}
|
||||
|
|
|
@ -4238,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
|
||||
*
|
||||
|
@ -4258,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
|
||||
*
|
||||
|
|
|
@ -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'];
|
||||
}
|
||||
|
@ -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] = [];
|
||||
|
@ -255,7 +319,7 @@ class OpenAPI3 extends Format
|
|||
case 'Utopia\Validator\JSON':
|
||||
case 'Utopia\Validator\Mock':
|
||||
case 'Utopia\Validator\Assoc':
|
||||
$param['default'] = (empty($param['default'])) ? new stdClass() : $param['default'];
|
||||
$param['default'] = (empty($param['default'])) ? new \stdClass() : $param['default'];
|
||||
$node['schema']['type'] = 'object';
|
||||
$node['schema']['x-example'] = '{}';
|
||||
//$node['schema']['format'] = 'json';
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,6 @@ namespace Appwrite\Specification\Format;
|
|||
|
||||
use Appwrite\Specification\Format;
|
||||
use Appwrite\Template\Template;
|
||||
use stdClass;
|
||||
use Utopia\Validator;
|
||||
|
||||
class Swagger2 extends Format
|
||||
|
@ -21,6 +20,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 +96,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'];
|
||||
}
|
||||
|
@ -125,7 +152,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 +182,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 +214,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 +258,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 +268,7 @@ class Swagger2 extends Format
|
|||
$temp['x-appwrite']['auth'] = array_slice($securities, 0, $this->authCount);
|
||||
$temp['security'][] = $securities;
|
||||
}
|
||||
|
||||
|
||||
$body = [
|
||||
'name' => 'payload',
|
||||
'in' => 'body',
|
||||
|
@ -252,7 +316,7 @@ class Swagger2 extends Format
|
|||
case 'Utopia\Validator\Mock':
|
||||
case 'Utopia\Validator\Assoc':
|
||||
$node['type'] = 'object';
|
||||
$param['default'] = (empty($param['default'])) ? new stdClass() : $param['default'];
|
||||
$param['default'] = (empty($param['default'])) ? new \stdClass() : $param['default'];
|
||||
$node['x-example'] = '{}';
|
||||
//$node['format'] = 'json';
|
||||
break;
|
||||
|
@ -354,15 +418,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 +441,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 +456,7 @@ class Swagger2 extends Format
|
|||
case 'json':
|
||||
$type = 'string';
|
||||
break;
|
||||
|
||||
|
||||
case 'integer':
|
||||
$type = 'integer';
|
||||
$format = 'int32';
|
||||
|
@ -408,19 +466,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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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,7 +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 stdClass;
|
||||
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;
|
||||
|
||||
/**
|
||||
* @method Response public function setStatusCode(int $code = 200)
|
||||
|
@ -56,19 +71,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 +158,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 +188,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 +209,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 +246,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)
|
||||
|
@ -277,7 +327,7 @@ class Response extends SwooleResponse
|
|||
$output = self::getFilter()->parse($output, $model);
|
||||
}
|
||||
|
||||
$this->json(!empty($output) ? $output : new stdClass());
|
||||
$this->json(!empty($output) ? $output : new \stdClass());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -302,7 +352,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 +367,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];
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -28,12 +28,6 @@ class Attribute extends Model
|
|||
'default' => '',
|
||||
'example' => 'available',
|
||||
])
|
||||
->addRule('size', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Attribute size.',
|
||||
'default' => 0,
|
||||
'example' => 128,
|
||||
])
|
||||
->addRule('required', [
|
||||
'type' => self::TYPE_BOOLEAN,
|
||||
'description' => 'Is attribute required?',
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
49
src/Appwrite/Utopia/Response/Model/AttributeBoolean.php
Normal file
49
src/Appwrite/Utopia/Response/Model/AttributeBoolean.php
Normal 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;
|
||||
}
|
||||
}
|
58
src/Appwrite/Utopia/Response/Model/AttributeEmail.php
Normal file
58
src/Appwrite/Utopia/Response/Model/AttributeEmail.php
Normal 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;
|
||||
}
|
||||
}
|
65
src/Appwrite/Utopia/Response/Model/AttributeFloat.php
Normal file
65
src/Appwrite/Utopia/Response/Model/AttributeFloat.php
Normal 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;
|
||||
}
|
||||
}
|
58
src/Appwrite/Utopia/Response/Model/AttributeIP.php
Normal file
58
src/Appwrite/Utopia/Response/Model/AttributeIP.php
Normal 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;
|
||||
}
|
||||
}
|
64
src/Appwrite/Utopia/Response/Model/AttributeInteger.php
Normal file
64
src/Appwrite/Utopia/Response/Model/AttributeInteger.php
Normal 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;
|
||||
}
|
||||
}
|
56
src/Appwrite/Utopia/Response/Model/AttributeList.php
Normal file
56
src/Appwrite/Utopia/Response/Model/AttributeList.php
Normal 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;
|
||||
}
|
||||
}
|
55
src/Appwrite/Utopia/Response/Model/AttributeString.php
Normal file
55
src/Appwrite/Utopia/Response/Model/AttributeString.php
Normal 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;
|
||||
}
|
||||
}
|
58
src/Appwrite/Utopia/Response/Model/AttributeURL.php
Normal file
58
src/Appwrite/Utopia/Response/Model/AttributeURL.php
Normal 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;
|
||||
}
|
||||
}
|
|
@ -4,7 +4,6 @@ namespace Appwrite\Utopia\Response\Model;
|
|||
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Model;
|
||||
use stdClass;
|
||||
|
||||
class Collection extends Model
|
||||
{
|
||||
|
@ -44,17 +43,25 @@ class Collection extends Model
|
|||
'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
|
||||
'example' => new \stdClass,
|
||||
'array' => true,
|
||||
])
|
||||
->addRule('indexes', [
|
||||
'type' => Response::MODEL_INDEX,
|
||||
'description' => 'Collection indexes.',
|
||||
'default' => [],
|
||||
'example' => new stdClass,
|
||||
'example' => new \stdClass,
|
||||
'array' => true
|
||||
])
|
||||
;
|
||||
|
|
47
src/Appwrite/Utopia/Response/Model/Metric.php
Normal file
47
src/Appwrite/Utopia/Response/Model/Metric.php
Normal 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;
|
||||
}
|
||||
}
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
namespace Appwrite\Utopia\Response\Model;
|
||||
|
||||
use stdClass;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Model;
|
||||
use Utopia\Config\Config;
|
||||
|
@ -100,28 +99,28 @@ class Project extends Model
|
|||
'type' => Response::MODEL_PLATFORM,
|
||||
'description' => 'List of Platforms.',
|
||||
'default' => [],
|
||||
'example' => new stdClass,
|
||||
'example' => new \stdClass,
|
||||
'array' => true,
|
||||
])
|
||||
->addRule('webhooks', [
|
||||
'type' => Response::MODEL_WEBHOOK,
|
||||
'description' => 'List of Webhooks.',
|
||||
'default' => [],
|
||||
'example' => new stdClass,
|
||||
'example' => new \stdClass,
|
||||
'array' => true,
|
||||
])
|
||||
->addRule('keys', [
|
||||
'type' => Response::MODEL_KEY,
|
||||
'description' => 'List of API Keys.',
|
||||
'default' => [],
|
||||
'example' => new stdClass,
|
||||
'example' => new \stdClass,
|
||||
'array' => true,
|
||||
])
|
||||
->addRule('domains', [
|
||||
'type' => Response::MODEL_DOMAIN,
|
||||
'description' => 'List of Domains.',
|
||||
'default' => [],
|
||||
'example' => new stdClass,
|
||||
'example' => new \stdClass,
|
||||
'array' => true,
|
||||
])
|
||||
;
|
||||
|
|
76
src/Appwrite/Utopia/Response/Model/UsageBuckets.php
Normal file
76
src/Appwrite/Utopia/Response/Model/UsageBuckets.php
Normal file
|
@ -0,0 +1,76 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Response\Model;
|
||||
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Model;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
76
src/Appwrite/Utopia/Response/Model/UsageCollection.php
Normal file
76
src/Appwrite/Utopia/Response/Model/UsageCollection.php
Normal file
|
@ -0,0 +1,76 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Response\Model;
|
||||
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Model;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
111
src/Appwrite/Utopia/Response/Model/UsageDatabase.php
Normal file
111
src/Appwrite/Utopia/Response/Model/UsageDatabase.php
Normal file
|
@ -0,0 +1,111 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Response\Model;
|
||||
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Model;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
62
src/Appwrite/Utopia/Response/Model/UsageFunctions.php
Normal file
62
src/Appwrite/Utopia/Response/Model/UsageFunctions.php
Normal file
|
@ -0,0 +1,62 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Response\Model;
|
||||
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Model;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
90
src/Appwrite/Utopia/Response/Model/UsageProject.php
Normal file
90
src/Appwrite/Utopia/Response/Model/UsageProject.php
Normal file
|
@ -0,0 +1,90 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Response\Model;
|
||||
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Model;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
55
src/Appwrite/Utopia/Response/Model/UsageStorage.php
Normal file
55
src/Appwrite/Utopia/Response/Model/UsageStorage.php
Normal file
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Response\Model;
|
||||
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Model;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
97
src/Appwrite/Utopia/Response/Model/UsageUsers.php
Normal file
97
src/Appwrite/Utopia/Response/Model/UsageUsers.php
Normal file
|
@ -0,0 +1,97 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Response\Model;
|
||||
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Model;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
23
tests/e2e/Scopes/SideConsole.php
Normal file
23
tests/e2e/Scopes/SideConsole.php
Normal 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';
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -73,7 +73,6 @@ trait DatabaseBase
|
|||
$this->assertEquals($releaseYear['headers']['status-code'], 201);
|
||||
$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);
|
||||
|
@ -101,6 +100,420 @@ trait DatabaseBase
|
|||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateAttributes
|
||||
*/
|
||||
|
@ -754,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(2);
|
||||
sleep(3);
|
||||
|
||||
$collection = $this->client->call(Client::METHOD_GET, '/database/collections/' . $collectionId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
|
@ -1089,4 +1502,224 @@ trait DatabaseBase
|
|||
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
||||
public function testEnforceCollectionPermissions()
|
||||
{
|
||||
$user = 'user:' . $this->getUser()['$id'];
|
||||
$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' => 'enforceCollectionPermissions',
|
||||
'permission' => 'collection',
|
||||
'read' => [$user],
|
||||
'write' => [$user]
|
||||
]);
|
||||
|
||||
$this->assertEquals($collection['headers']['status-code'], 201);
|
||||
$this->assertEquals($collection['body']['name'], 'enforceCollectionPermissions');
|
||||
$this->assertEquals($collection['body']['permission'], 'collection');
|
||||
|
||||
$collectionId = $collection['body']['$id'];
|
||||
|
||||
sleep(2);
|
||||
|
||||
$attribute = $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' => 'attribute',
|
||||
'size' => 64,
|
||||
'required' => true,
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $attribute['headers']['status-code'], 201);
|
||||
$this->assertEquals('attribute', $attribute['body']['key']);
|
||||
|
||||
// wait for db to add attribute
|
||||
sleep(2);
|
||||
|
||||
$index = $this->client->call(Client::METHOD_POST, '/database/collections/' . $collectionId . '/indexes', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'indexId' => 'key_attribute',
|
||||
'type' => 'key',
|
||||
'attributes' => [$attribute['body']['key']],
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $index['headers']['status-code']);
|
||||
$this->assertEquals('key_attribute', $index['body']['key']);
|
||||
|
||||
// wait for db to add attribute
|
||||
sleep(2);
|
||||
|
||||
$document1 = $this->client->call(Client::METHOD_POST, '/database/collections/' . $collectionId . '/documents', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'documentId' => 'unique()',
|
||||
'data' => [
|
||||
'attribute' => 'one',
|
||||
],
|
||||
'read' => [$user],
|
||||
'write' => [$user],
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $document1['headers']['status-code']);
|
||||
|
||||
$documents = $this->client->call(Client::METHOD_GET, '/database/collections/' . $collectionId . '/documents', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals(1, $documents['body']['sum']);
|
||||
$this->assertCount(1, $documents['body']['documents']);
|
||||
|
||||
/*
|
||||
* Test for Failure
|
||||
*/
|
||||
|
||||
// Remove write permission
|
||||
$collection = $this->client->call(Client::METHOD_PUT, '/database/collections/' . $collectionId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'name' => 'enforceCollectionPermissions',
|
||||
'permission' => 'collection',
|
||||
'read' => [$user],
|
||||
'write' => []
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $collection['headers']['status-code']);
|
||||
|
||||
$badDocument = $this->client->call(Client::METHOD_POST, '/database/collections/' . $collectionId . '/documents', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'documentId' => 'unique()',
|
||||
'data' => [
|
||||
'attribute' => 'bad',
|
||||
],
|
||||
'read' => [$user],
|
||||
'write' => [$user],
|
||||
]);
|
||||
|
||||
if($this->getSide() == 'client') {
|
||||
$this->assertEquals(401, $badDocument['headers']['status-code']);
|
||||
}
|
||||
|
||||
if($this->getSide() == 'server') {
|
||||
$this->assertEquals(201, $badDocument['headers']['status-code']);
|
||||
}
|
||||
|
||||
// Remove read permission
|
||||
$collection = $this->client->call(Client::METHOD_PUT, '/database/collections/' . $collectionId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'name' => 'enforceCollectionPermissions',
|
||||
'permission' => 'collection',
|
||||
'read' => [],
|
||||
'write' => []
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $collection['headers']['status-code']);
|
||||
|
||||
$documents = $this->client->call(Client::METHOD_GET, '/database/collections/' . $collectionId . '/documents', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
]));
|
||||
|
||||
$this->assertEquals(404, $documents['headers']['status-code']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
}
|
125
tests/e2e/Services/Database/DatabaseConsoleClientTest.php
Normal file
125
tests/e2e/Services/Database/DatabaseConsoleClientTest.php
Normal 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']);
|
||||
}
|
||||
}
|
|
@ -241,6 +241,214 @@ class DatabaseCustomServerTest extends Scope
|
|||
/**
|
||||
* @depends testDeleteIndex
|
||||
*/
|
||||
public function testDeleteIndexOnDeleteAttribute($data)
|
||||
{
|
||||
$attribute1 = $this->client->call(Client::METHOD_POST, '/database/collections/' . $data['collectionId'] . '/attributes/string', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'attributeId' => 'attribute1',
|
||||
'size' => 16,
|
||||
'required' => true,
|
||||
]);
|
||||
|
||||
$attribute2 = $this->client->call(Client::METHOD_POST, '/database/collections/' . $data['collectionId'] . '/attributes/string', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'attributeId' => 'attribute2',
|
||||
'size' => 16,
|
||||
'required' => true,
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $attribute1['headers']['status-code']);
|
||||
$this->assertEquals(201, $attribute2['headers']['status-code']);
|
||||
$this->assertEquals('attribute1', $attribute1['body']['key']);
|
||||
$this->assertEquals('attribute2', $attribute2['body']['key']);
|
||||
|
||||
sleep(2);
|
||||
|
||||
$index1 = $this->client->call(Client::METHOD_POST, '/database/collections/' . $data['collectionId'] . '/indexes', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'indexId' => 'index1',
|
||||
'type' => 'key',
|
||||
'attributes' => ['attribute1', 'attribute2'],
|
||||
'orders' => ['ASC', 'ASC'],
|
||||
]);
|
||||
|
||||
$index2 = $this->client->call(Client::METHOD_POST, '/database/collections/' . $data['collectionId'] . '/indexes', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'indexId' => 'index2',
|
||||
'type' => 'key',
|
||||
'attributes' => ['attribute2'],
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $index1['headers']['status-code']);
|
||||
$this->assertEquals(201, $index2['headers']['status-code']);
|
||||
$this->assertEquals('index1', $index1['body']['key']);
|
||||
$this->assertEquals('index2', $index2['body']['key']);
|
||||
|
||||
sleep(2);
|
||||
|
||||
// Expected behavior: deleting attribute2 will cause index2 to be dropped, and index1 rebuilt with a single key
|
||||
$deleted = $this->client->call(Client::METHOD_DELETE, '/database/collections/' . $data['collectionId'] . '/attributes/'. $attribute2['body']['key'], array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]));
|
||||
|
||||
$this->assertEquals($deleted['headers']['status-code'], 204);
|
||||
|
||||
// wait for database worker to complete
|
||||
sleep(2);
|
||||
|
||||
$collection = $this->client->call(Client::METHOD_GET, '/database/collections/' . $data['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']);
|
||||
$this->assertIsArray($collection['body']['indexes']);
|
||||
$this->assertCount(1, $collection['body']['indexes']);
|
||||
$this->assertEquals($index1['body']['key'], $collection['body']['indexes'][0]['key']);
|
||||
$this->assertIsArray($collection['body']['indexes'][0]['attributes']);
|
||||
$this->assertCount(1, $collection['body']['indexes'][0]['attributes']);
|
||||
$this->assertEquals($attribute1['body']['key'], $collection['body']['indexes'][0]['attributes'][0]);
|
||||
|
||||
// Delete attribute
|
||||
$deleted = $this->client->call(Client::METHOD_DELETE, '/database/collections/' . $data['collectionId'] . '/attributes/' . $attribute1['body']['key'], array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]));
|
||||
|
||||
$this->assertEquals($deleted['headers']['status-code'], 204);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function testCleanupDuplicateIndexOnDeleteAttribute()
|
||||
{
|
||||
$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' => 'TestCleanupDuplicateIndexOnDeleteAttribute',
|
||||
'read' => ['role:all'],
|
||||
'write' => ['role:all'],
|
||||
'permission' => 'document',
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $collection['headers']['status-code']);
|
||||
$this->assertNotEmpty($collection['body']['$id']);
|
||||
|
||||
$collectionId = $collection['body']['$id'];
|
||||
|
||||
$attribute1 = $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' => 'attribute1',
|
||||
'size' => 16,
|
||||
'required' => true,
|
||||
]);
|
||||
|
||||
$attribute2 = $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' => 'attribute2',
|
||||
'size' => 16,
|
||||
'required' => true,
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $attribute1['headers']['status-code']);
|
||||
$this->assertEquals(201, $attribute2['headers']['status-code']);
|
||||
$this->assertEquals('attribute1', $attribute1['body']['key']);
|
||||
$this->assertEquals('attribute2', $attribute2['body']['key']);
|
||||
|
||||
sleep(2);
|
||||
|
||||
$index1 = $this->client->call(Client::METHOD_POST, '/database/collections/' . $collectionId . '/indexes', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'indexId' => 'index1',
|
||||
'type' => 'key',
|
||||
'attributes' => ['attribute1', 'attribute2'],
|
||||
'orders' => ['ASC', 'ASC'],
|
||||
]);
|
||||
|
||||
$index2 = $this->client->call(Client::METHOD_POST, '/database/collections/' . $collectionId . '/indexes', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'indexId' => 'index2',
|
||||
'type' => 'key',
|
||||
'attributes' => ['attribute2'],
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $index1['headers']['status-code']);
|
||||
$this->assertEquals(201, $index2['headers']['status-code']);
|
||||
$this->assertEquals('index1', $index1['body']['key']);
|
||||
$this->assertEquals('index2', $index2['body']['key']);
|
||||
|
||||
sleep(2);
|
||||
|
||||
// Expected behavior: deleting attribute1 would cause index1 to be a duplicate of index2 and automatically removed
|
||||
$deleted = $this->client->call(Client::METHOD_DELETE, '/database/collections/' . $collectionId . '/attributes/'. $attribute1['body']['key'], array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]));
|
||||
|
||||
$this->assertEquals($deleted['headers']['status-code'], 204);
|
||||
|
||||
// wait for database worker to complete
|
||||
sleep(2);
|
||||
|
||||
$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']);
|
||||
$this->assertIsArray($collection['body']['indexes']);
|
||||
$this->assertCount(1, $collection['body']['indexes']);
|
||||
$this->assertEquals($index2['body']['key'], $collection['body']['indexes'][0]['key']);
|
||||
$this->assertIsArray($collection['body']['indexes'][0]['attributes']);
|
||||
$this->assertCount(1, $collection['body']['indexes'][0]['attributes']);
|
||||
$this->assertEquals($attribute2['body']['key'], $collection['body']['indexes'][0]['attributes'][0]);
|
||||
|
||||
// Delete attribute
|
||||
$deleted = $this->client->call(Client::METHOD_DELETE, '/database/collections/' . $collectionId . '/attributes/' . $attribute2['body']['key'], array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]));
|
||||
|
||||
$this->assertEquals($deleted['headers']['status-code'], 204);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testDeleteIndexOnDeleteAttribute
|
||||
*/
|
||||
public function testDeleteCollection($data)
|
||||
{
|
||||
$collectionId = $data['collectionId'];
|
||||
|
@ -307,6 +515,101 @@ class DatabaseCustomServerTest extends Scope
|
|||
$this->assertEquals($response['headers']['status-code'], 404);
|
||||
}
|
||||
|
||||
public function testAttributeCountLimit()
|
||||
{
|
||||
$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' => 'attributeCountLimit',
|
||||
'read' => ['role:all'],
|
||||
'write' => ['role:all'],
|
||||
'permission' => 'document',
|
||||
]);
|
||||
|
||||
$collectionId = $collection['body']['$id'];
|
||||
|
||||
// load the collection up to the limit
|
||||
for ($i=0; $i < 1012; $i++) {
|
||||
$attribute = $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' => "attribute{$i}",
|
||||
'required' => false,
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $attribute['headers']['status-code']);
|
||||
}
|
||||
|
||||
sleep(5);
|
||||
|
||||
$tooMany = $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' => "tooMany",
|
||||
'required' => false,
|
||||
]);
|
||||
|
||||
$this->assertEquals(400, $tooMany['headers']['status-code']);
|
||||
$this->assertEquals('Attribute limit exceeded', $tooMany['body']['message']);
|
||||
}
|
||||
|
||||
public function testAttributeRowWidthLimit()
|
||||
{
|
||||
$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' => 'attributeRowWidthLimit',
|
||||
'name' => 'attributeRowWidthLimit',
|
||||
'read' => ['role:all'],
|
||||
'write' => ['role:all'],
|
||||
'permission' => 'document',
|
||||
]);
|
||||
|
||||
$this->assertEquals($collection['headers']['status-code'], 201);
|
||||
$this->assertEquals($collection['body']['name'], 'attributeRowWidthLimit');
|
||||
|
||||
$collectionId = $collection['body']['$id'];
|
||||
|
||||
// Add wide string attributes to approach row width limit
|
||||
for ($i=0; $i < 15; $i++) {
|
||||
$attribute = $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' => "attribute{$i}",
|
||||
'size' => 1024,
|
||||
'required' => true,
|
||||
]);
|
||||
|
||||
$this->assertEquals($attribute['headers']['status-code'], 201);
|
||||
}
|
||||
|
||||
sleep(5);
|
||||
|
||||
$tooWide = $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' => 'tooWide',
|
||||
'size' => 1024,
|
||||
'required' => true,
|
||||
]);
|
||||
|
||||
$this->assertEquals(400, $tooWide['headers']['status-code']);
|
||||
$this->assertEquals('Attribute limit exceeded', $tooWide['body']['message']);
|
||||
}
|
||||
|
||||
public function testIndexLimitException()
|
||||
{
|
||||
$collection = $this->client->call(Client::METHOD_POST, '/database/collections', array_merge([
|
||||
|
@ -403,5 +706,13 @@ class DatabaseCustomServerTest extends Scope
|
|||
|
||||
$this->assertEquals(400, $tooMany['headers']['status-code']);
|
||||
$this->assertEquals('Index limit exceeded', $tooMany['body']['message']);
|
||||
|
||||
$collection = $this->client->call(Client::METHOD_DELETE, '/database/collections/' . $collectionId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]));
|
||||
|
||||
$this->assertEquals(204, $collection['headers']['status-code']);
|
||||
}
|
||||
}
|
91
tests/e2e/Services/Functions/FunctionsConsoleClientTest.php
Normal file
91
tests/e2e/Services/Functions/FunctionsConsoleClientTest.php
Normal file
|
@ -0,0 +1,91 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\E2E\Services\Functions;
|
||||
|
||||
use Tests\E2E\Scopes\Scope;
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Client;
|
||||
use Tests\E2E\Scopes\SideConsole;
|
||||
|
||||
class FunctionsConsoleClientTest extends Scope
|
||||
{
|
||||
use ProjectCustom;
|
||||
use SideConsole;
|
||||
|
||||
public function testCreateFunction():array
|
||||
{
|
||||
$function = $this->client->call(Client::METHOD_POST, '/functions', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'functionId' => 'unique()',
|
||||
'name' => 'Test',
|
||||
'execute' => ['user:'.$this->getUser()['$id']],
|
||||
'runtime' => 'php-8.0',
|
||||
'vars' => [
|
||||
'funcKey1' => 'funcValue1',
|
||||
'funcKey2' => 'funcValue2',
|
||||
'funcKey3' => 'funcValue3',
|
||||
],
|
||||
'events' => [
|
||||
'account.create',
|
||||
'account.delete',
|
||||
],
|
||||
'schedule' => '0 0 1 1 *',
|
||||
'timeout' => 10,
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $function['headers']['status-code']);
|
||||
|
||||
return [
|
||||
'functionId' => $function['body']['$id']
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateFunction
|
||||
*/
|
||||
public function testGetCollectionUsage(array $data)
|
||||
{
|
||||
/**
|
||||
* Test for FAILURE
|
||||
*/
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/functions/'.$data['functionId'].'/usage', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id']
|
||||
], $this->getHeaders()), [
|
||||
'range' => '232h'
|
||||
]);
|
||||
|
||||
$this->assertEquals(400, $response['headers']['status-code']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/functions/randomFunctionId/usage', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id']
|
||||
], $this->getHeaders()), [
|
||||
'range' => '24h'
|
||||
]);
|
||||
|
||||
$this->assertEquals(404, $response['headers']['status-code']);
|
||||
|
||||
/**
|
||||
* Test for SUCCESS
|
||||
*/
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/functions/'.$data['functionId'].'/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']), 4);
|
||||
$this->assertEquals($response['body']['range'], '24h');
|
||||
$this->assertIsArray($response['body']['functions.executions']);
|
||||
$this->assertIsArray($response['body']['functions.failures']);
|
||||
$this->assertIsArray($response['body']['functions.compute']);
|
||||
}
|
||||
|
||||
}
|
|
@ -215,24 +215,16 @@ class ProjectsConsoleClientTest extends Scope
|
|||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals(count($response['body']), 8);
|
||||
$this->assertNotEmpty($response['body']);
|
||||
$this->assertArrayHasKey('collections', $response['body']);
|
||||
$this->assertArrayHasKey('documents', $response['body']);
|
||||
$this->assertArrayHasKey('network', $response['body']);
|
||||
$this->assertArrayHasKey('requests', $response['body']);
|
||||
$this->assertArrayHasKey('storage', $response['body']);
|
||||
$this->assertArrayHasKey('users', $response['body']);
|
||||
$this->assertIsArray($response['body']['collections']['data']);
|
||||
$this->assertIsInt($response['body']['collections']['total']);
|
||||
$this->assertIsArray($response['body']['documents']['data']);
|
||||
$this->assertIsInt($response['body']['documents']['total']);
|
||||
$this->assertIsArray($response['body']['network']['data']);
|
||||
$this->assertIsInt($response['body']['network']['total']);
|
||||
$this->assertIsArray($response['body']['requests']['data']);
|
||||
$this->assertIsInt($response['body']['requests']['total']);
|
||||
$this->assertIsInt($response['body']['storage']['total']);
|
||||
$this->assertIsArray($response['body']['users']['data']);
|
||||
$this->assertIsInt($response['body']['users']['total']);
|
||||
$this->assertEquals('30d', $response['body']['range']);
|
||||
$this->assertIsArray($response['body']['requests']);
|
||||
$this->assertIsArray($response['body']['network']);
|
||||
$this->assertIsArray($response['body']['functions']);
|
||||
$this->assertIsArray($response['body']['documents']);
|
||||
$this->assertIsArray($response['body']['collections']);
|
||||
$this->assertIsArray($response['body']['users']);
|
||||
$this->assertIsArray($response['body']['storage']);
|
||||
|
||||
/**
|
||||
* Test for FAILURE
|
||||
|
|
|
@ -2,13 +2,91 @@
|
|||
|
||||
namespace Tests\E2E\Services\Storage;
|
||||
|
||||
use Tests\E2E\Client;
|
||||
use Tests\E2E\Scopes\Scope;
|
||||
use Tests\E2E\Scopes\ProjectConsole;
|
||||
use Tests\E2E\Scopes\SideClient;
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Scopes\SideConsole;
|
||||
|
||||
class StorageConsoleClientTest extends Scope
|
||||
{
|
||||
use SideConsole;
|
||||
use StorageBase;
|
||||
use ProjectConsole;
|
||||
use SideClient;
|
||||
use ProjectCustom;
|
||||
|
||||
public function testGetStorageUsage()
|
||||
{
|
||||
/**
|
||||
* Test for FAILURE
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_GET, '/storage/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, '/storage/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']), 3);
|
||||
$this->assertEquals($response['body']['range'], '24h');
|
||||
$this->assertIsArray($response['body']['storage']);
|
||||
$this->assertIsArray($response['body']['files']);
|
||||
}
|
||||
|
||||
public function testGetStorageBucketUsage()
|
||||
{
|
||||
/**
|
||||
* Test for FAILURE
|
||||
*/
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/storage/default/usage', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id']
|
||||
], $this->getHeaders()), [
|
||||
'range' => '32h'
|
||||
]);
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 400);
|
||||
|
||||
// TODO: Uncomment once we implement check for missing bucketId in the usage endpoint.
|
||||
|
||||
// $response = $this->client->call(Client::METHOD_GET, '/storage/randomBucketId/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, '/storage/default/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']['files.count']);
|
||||
$this->assertIsArray($response['body']['files.create']);
|
||||
$this->assertIsArray($response['body']['files.read']);
|
||||
$this->assertIsArray($response['body']['files.update']);
|
||||
$this->assertIsArray($response['body']['files.delete']);
|
||||
}
|
||||
}
|
|
@ -150,6 +150,95 @@ trait UsersBase
|
|||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testGetUser
|
||||
*/
|
||||
public function testUpdateUserName(array $data):array
|
||||
{
|
||||
/**
|
||||
* Test for SUCCESS
|
||||
*/
|
||||
$user = $this->client->call(Client::METHOD_PATCH, '/users/' . $data['userId'] . '/name', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'name' => 'Updated name',
|
||||
]);
|
||||
|
||||
$this->assertEquals($user['headers']['status-code'], 200);
|
||||
$this->assertEquals($user['body']['name'], 'Updated name');
|
||||
|
||||
$user = $this->client->call(Client::METHOD_GET, '/users/' . $data['userId'], array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals($user['headers']['status-code'], 200);
|
||||
$this->assertEquals($user['body']['name'], 'Updated name');
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testGetUser
|
||||
*/
|
||||
public function testUpdateUserEmail(array $data):array
|
||||
{
|
||||
/**
|
||||
* Test for SUCCESS
|
||||
*/
|
||||
$user = $this->client->call(Client::METHOD_PATCH, '/users/' . $data['userId'] . '/email', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'email' => 'users.service@updated.com',
|
||||
]);
|
||||
|
||||
$this->assertEquals($user['headers']['status-code'], 200);
|
||||
$this->assertEquals($user['body']['email'], 'users.service@updated.com');
|
||||
|
||||
$user = $this->client->call(Client::METHOD_GET, '/users/' . $data['userId'], array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals($user['headers']['status-code'], 200);
|
||||
$this->assertEquals($user['body']['email'], 'users.service@updated.com');
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testUpdateUserEmail
|
||||
*/
|
||||
public function testUpdateUserPassword(array $data):array
|
||||
{
|
||||
/**
|
||||
* Test for SUCCESS
|
||||
*/
|
||||
$user = $this->client->call(Client::METHOD_PATCH, '/users/' . $data['userId'] . '/password', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'password' => 'password2',
|
||||
]);
|
||||
|
||||
$this->assertEquals($user['headers']['status-code'], 200);
|
||||
$this->assertNotEmpty($user['body']['$id']);
|
||||
|
||||
$session = $this->client->call(Client::METHOD_POST, '/account/sessions', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], [
|
||||
'email' => 'users.service@updated.com',
|
||||
'password' => 'password2'
|
||||
]);
|
||||
|
||||
$this->assertEquals($session['headers']['status-code'], 201);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testGetUser
|
||||
*/
|
||||
|
|
83
tests/e2e/Services/Users/UsersConsoleClientTest.php
Normal file
83
tests/e2e/Services/Users/UsersConsoleClientTest.php
Normal file
|
@ -0,0 +1,83 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\E2E\Services\Users;
|
||||
|
||||
use Tests\E2E\Scopes\Scope;
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Client;
|
||||
use Tests\E2E\Scopes\SideConsole;
|
||||
|
||||
class UsersConsoleClientTest extends Scope
|
||||
{
|
||||
use ProjectCustom;
|
||||
use SideConsole;
|
||||
|
||||
public function testGetUsersUsage()
|
||||
{
|
||||
/**
|
||||
* Test for FAILURE
|
||||
*/
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/users/usage', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id']
|
||||
], $this->getHeaders()), [
|
||||
'range' => '32h',
|
||||
'provider' => 'email'
|
||||
]);
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 400);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/users/usage', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id']
|
||||
], $this->getHeaders()), [
|
||||
'range' => '24h',
|
||||
'provider' => 'some-random-provider'
|
||||
]);
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 400);
|
||||
|
||||
/**
|
||||
* Test for SUCCESS
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_GET, '/users/usage', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id']
|
||||
], $this->getHeaders()), [
|
||||
'range' => '24h',
|
||||
'provider' => 'email'
|
||||
]);
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 200);
|
||||
$this->assertEquals(count($response['body']), 9);
|
||||
$this->assertEquals($response['body']['range'], '24h');
|
||||
$this->assertIsArray($response['body']['users.count']);
|
||||
$this->assertIsArray($response['body']['users.create']);
|
||||
$this->assertIsArray($response['body']['users.read']);
|
||||
$this->assertIsArray($response['body']['users.update']);
|
||||
$this->assertIsArray($response['body']['users.delete']);
|
||||
$this->assertIsArray($response['body']['sessions.create']);
|
||||
$this->assertIsArray($response['body']['sessions.provider.create']);
|
||||
$this->assertIsArray($response['body']['sessions.delete']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/users/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']), 9);
|
||||
$this->assertEquals($response['body']['range'], '24h');
|
||||
$this->assertIsArray($response['body']['users.count']);
|
||||
$this->assertIsArray($response['body']['users.create']);
|
||||
$this->assertIsArray($response['body']['users.read']);
|
||||
$this->assertIsArray($response['body']['users.update']);
|
||||
$this->assertIsArray($response['body']['users.delete']);
|
||||
$this->assertIsArray($response['body']['sessions.create']);
|
||||
$this->assertIsArray($response['body']['sessions.provider.create']);
|
||||
$this->assertIsArray($response['body']['sessions.delete']);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue