1
0
Fork 0
mirror of synced 2024-06-27 02:31:04 +12:00

Merge remote-tracking branch 'origin/0.14.x' into feat-sessions-subquery

This commit is contained in:
Matej Bačo 2022-05-08 18:50:41 +00:00
commit 4c642b99c5
84 changed files with 1906 additions and 28062 deletions

View file

@ -12,7 +12,7 @@ RUN composer install --ignore-platform-reqs --optimize-autoloader \
--no-plugins --no-scripts --prefer-dist \
`if [ "$TESTING" != "true" ]; then echo "--no-dev"; fi`
FROM node:16.13.2-alpine3.15 as node
FROM node:16.14.2-alpine3.15 as node
WORKDIR /usr/local/src/
@ -30,8 +30,8 @@ ARG DEBUG=false
ENV DEBUG=$DEBUG
ENV PHP_REDIS_VERSION=5.3.7 \
PHP_MONGODB_VERSION=1.9.1 \
PHP_SWOOLE_VERSION=v4.8.7 \
PHP_MONGODB_VERSION=1.13.0 \
PHP_SWOOLE_VERSION=v4.8.9 \
PHP_IMAGICK_VERSION=3.7.0 \
PHP_YAML_VERSION=2.2.2 \
PHP_MAXMINDDB_VERSION=v1.11.0

View file

@ -1065,9 +1065,9 @@ $collections = [
'size' => 16384,
'signed' => true,
'required' => false,
'default' => [],
'array' => true,
'filters' => ['json'],
'default' => null,
'array' => false,
'filters' => ['subQueryTokens'],
],
[
'$id' => 'memberships',
@ -1077,8 +1077,8 @@ $collections = [
'signed' => true,
'required' => false,
'default' => [],
'array' => true,
'filters' => ['json'],
'array' => false,
'filters' => ['subQueryMemberships'],
],
[
'$id' => 'search',
@ -1128,6 +1128,89 @@ $collections = [
],
],
'tokens' => [
'$collection' => Database::METADATA,
'$id' => 'tokens',
'name' => 'Tokens',
'attributes' => [
[
'$id' => 'userId',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'type',
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => 0,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'secret',
'type' => Database::VAR_STRING,
'format' => '',
'size' => 512, // https://www.tutorialspoint.com/how-long-is-the-sha256-hash-in-mysql (512 for encryption)
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => ['encrypt'],
],
[
'$id' => 'expire',
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => 0,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'userAgent',
'type' => Database::VAR_STRING,
'format' => '',
'size' => 16384,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'ip',
'type' => Database::VAR_STRING,
'format' => '',
'size' => 45, // https://stackoverflow.com/a/166157/2299554
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
]
],
'indexes' => [
[
'$id' => '_key_user',
'type' => Database::INDEX_KEY,
'attributes' => ['userId'],
'lengths' => [Database::LENGTH_KEY],
'orders' => [Database::ORDER_ASC],
],
],
],
'sessions' => [
'$collection' => Database::METADATA,
'$id' => 'sessions',
@ -2128,7 +2211,7 @@ $collections = [
'filters' => [],
],
[
'$id' => 'stdout',
'$id' => 'response',
'type' => Database::VAR_STRING,
'format' => '',
'size' => 16384,
@ -2432,7 +2515,7 @@ $collections = [
'$id' => 'value',
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => 0,
'size' => 8,
'signed' => false,
'required' => true,
'default' => null,

View file

@ -21,6 +21,16 @@ return [ // Ordered by ABC.
'beta' => true,
'mock' => false,
],
'auth0' => [
'name' => 'Auth0',
'developers' => 'https://auth0.com/developers',
'icon' => 'icon-auth0',
'enabled' => true,
'sandbox' => false,
'form' => 'auth0.phtml',
'beta' => false,
'mock' => false,
],
'bitbucket' => [
'name' => 'BitBucket',
'developers' => 'https://developer.atlassian.com/bitbucket',
@ -141,6 +151,16 @@ return [ // Ordered by ABC.
'beta' => false,
'mock' => false,
],
'okta' => [
'name' => 'Okta',
'developers' => 'https://developer.okta.com/',
'icon' => 'icon-okta',
'enabled' => true,
'sandbox' => false,
'form' => 'okta.phtml',
'beta' => false,
'mock' => false,
],
'paypal' => [
'name' => 'PayPal',
'developers' => 'https://developer.paypal.com/docs/api/overview/',

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -466,7 +466,7 @@ return [
'name' => '_APP_STORAGE_S3_REGION',
'description' => 'AWS S3 storage region. Required when storage adapter is set to S3. You can find your region info for your bucket from AWS console.',
'introduction' => '0.13.0',
'default' => 'us-eas-1',
'default' => 'us-east-1',
'required' => false,
'question' => '',
],
@ -498,7 +498,7 @@ return [
'name' => '_APP_STORAGE_DO_SPACES_REGION',
'description' => 'DigitalOcean spaces region. Required when storage adapter is set to DOSpaces. You can find your region info for your space from DigitalOcean console.',
'introduction' => '0.13.0',
'default' => 'us-eas-1',
'default' => 'us-east-1',
'required' => false,
'question' => '',
],

View file

@ -105,8 +105,8 @@ App::post('/v1/account')
'name' => $name,
'prefs' => new \stdClass(),
'sessions' => null,
'tokens' => [],
'memberships' => [],
'tokens' => null,
'memberships' => null,
'search' => implode(' ', [$userId, $email, $name]),
'deleted' => false
])));
@ -502,8 +502,8 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
'name' => $name,
'prefs' => new \stdClass(),
'sessions' => null,
'tokens' => [],
'memberships' => [],
'tokens' => null,
'memberships' => null,
'search' => implode(' ', [$userId, $email, $name]),
'deleted' => false
])));
@ -677,8 +677,8 @@ App::post('/v1/account/sessions/magic-url')
'reset' => false,
'prefs' => new \stdClass(),
'sessions' => null,
'tokens' => [],
'memberships' => [],
'tokens' => null,
'memberships' => null,
'search' => implode(' ', [$userId, $email]),
'deleted' => false
])));
@ -703,13 +703,12 @@ App::post('/v1/account/sessions/magic-url')
Authorization::setRole('user:'.$user->getId());
$user->setAttribute('tokens', $token, Document::SET_TYPE_APPEND);
$token = $dbForProject->createDocument('tokens', $token
->setAttribute('$read', ['user:'.$user->getId()])
->setAttribute('$write', ['user:'.$user->getId()])
);
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
if (false === $user) {
throw new Exception('Failed to save user to DB', 500, Exception::GENERAL_SERVER_ERROR);
}
$dbForProject->deleteCachedDocument('users', $user->getId());
if(empty($url)) {
$url = $request->getProtocol().'://'.$request->getHostname().'/auth/magic-url';
@ -783,7 +782,7 @@ App::put('/v1/account/sessions/magic-url')
/** @var MaxMind\Db\Reader $geodb */
/** @var Appwrite\Event\Event $audits */
$user = $dbForProject->getDocument('users', $userId);
$user = Authorization::skip(fn() => $dbForProject->getDocument('users', $userId));
if ($user->isEmpty() || $user->getAttribute('deleted')) {
throw new Exception('User not found', 404, Exception::USER_NOT_FOUND);
@ -830,11 +829,8 @@ App::put('/v1/account/sessions/magic-url')
* We act like we're updating and validating
* the recovery token but actually we don't need it anymore.
*/
foreach ($tokens as $key => $singleToken) {
if ($token === $singleToken->getId()) {
unset($tokens[$key]);
}
}
$dbForProject->deleteDocument('tokens', $token);
$dbForProject->deleteCachedDocument('users', $user->getId());
$user
->setAttribute('tokens', $tokens);
@ -865,9 +861,7 @@ App::put('/v1/account/sessions/magic-url')
->setStatusCode(Response::STATUS_CODE_CREATED)
;
$countryName = (isset($countries[strtoupper($session->getAttribute('countryCode'))]))
? $countries[strtoupper($session->getAttribute('countryCode'))]
: $locale->getText('locale.country.unknown');
$countryName = $locale->getText('countries.'.strtolower($session->getAttribute('countryCode')), $locale->getText('locale.country.unknown'));
$session
->setAttribute('current', true)
@ -949,8 +943,8 @@ App::post('/v1/account/sessions/anonymous')
'name' => null,
'prefs' => new \stdClass(),
'sessions' => null,
'tokens' => [],
'memberships' => [],
'tokens' => null,
'memberships' => null,
'search' => $userId,
'deleted' => false
])));
@ -1009,9 +1003,7 @@ App::post('/v1/account/sessions/anonymous')
->setStatusCode(Response::STATUS_CODE_CREATED)
;
$countryName = (isset($countries[strtoupper($session->getAttribute('countryCode'))]))
? $countries[strtoupper($session->getAttribute('countryCode'))]
: $locale->getText('locale.country.unknown');
$countryName = $locale->getText('countries.'.strtolower($session->getAttribute('countryCode')), $locale->getText('locale.country.unknown'));
$session
->setAttribute('current', true)
@ -1277,15 +1269,13 @@ App::get('/v1/account/sessions/:sessionId')
$sessions = $user->getAttribute('sessions', []);
$sessionId = ($sessionId === 'current')
? Auth::sessionVerify($user->getAttribute('sessions'), Auth::$secret)
: $sessionId;
? Auth::sessionVerify($user->getAttribute('sessions'), Auth::$secret)
: $sessionId;
foreach ($sessions as $session) {/** @var Document $session */
if ($sessionId == $session->getId()) {
$countryName = (isset($countries[strtoupper($session->getAttribute('countryCode'))]))
? $countries[strtoupper($session->getAttribute('countryCode'))]
: $locale->getText('locale.country.unknown');
$countryName = $locale->getText('countries.'.strtolower($session->getAttribute('countryCode')), $locale->getText('locale.country.unknown'));
$session
->setAttribute('current', ($session->getAttribute('secret') == Auth::hash(Auth::$secret)))
@ -1619,7 +1609,7 @@ App::delete('/v1/account/sessions/:sessionId')
if ($session->getAttribute('secret') == Auth::hash(Auth::$secret)) { // If current session delete the cookies too
$session
->setAttribute('current', true)
->setAttribute('countryName', (isset($countries[strtoupper($session->getAttribute('countryCode'))])) ? $countries[strtoupper($session->getAttribute('countryCode'))] : $locale->getText('locale.country.unknown'))
->setAttribute('countryName', $locale->getText('countries.'.strtolower($session->getAttribute('countryCode')), $locale->getText('locale.country.unknown')))
;
if (!Config::getParam('domainVerification')) {
@ -1802,7 +1792,7 @@ App::delete('/v1/account/sessions')
$session
->setAttribute('current', false)
->setAttribute('countryName', (isset($countries[strtoupper($session->getAttribute('countryCode'))])) ? $countries[strtoupper($session->getAttribute('countryCode'))] : $locale->getText('locale.country.unknown'))
->setAttribute('countryName', $locale->getText('countries.'.strtolower($session->getAttribute('countryCode')), $locale->getText('locale.country.unknown')))
;
if ($session->getAttribute('secret') == Auth::hash(Auth::$secret)) { // If current session delete the cookies too
@ -1902,9 +1892,12 @@ App::post('/v1/account/recovery')
Authorization::setRole('user:' . $profile->getId());
$profile->setAttribute('tokens', $recovery, Document::SET_TYPE_APPEND);
$recovery = $dbForProject->createDocument('tokens', $recovery
->setAttribute('$read', ['user:'.$profile->getId()])
->setAttribute('$write', ['user:'.$profile->getId()])
);
$profile = $dbForProject->updateDocument('users', $profile->getId(), $profile);
$dbForProject->deleteCachedDocument('users', $profile->getId());
$url = Template::parseURL($url);
$url['query'] = Template::mergeQuery(((isset($url['query'])) ? $url['query'] : ''), ['userId' => $profile->getId(), 'secret' => $secret, 'expire' => $expire]);
@ -1999,18 +1992,14 @@ App::put('/v1/account/recovery')
->setAttribute('emailVerification', true)
);
$recoveryDocument = $dbForProject->getDocument('tokens', $recovery);
/**
* We act like we're updating and validating
* the recovery token but actually we don't need it anymore.
*/
foreach ($tokens as $key => $token) {
if ($recovery === $token->getId()) {
$recovery = $token;
unset($tokens[$key]);
}
}
$dbForProject->updateDocument('users', $profile->getId(), $profile->setAttribute('tokens', $tokens));
$dbForProject->deleteDocument('tokens', $recovery);
$dbForProject->deleteCachedDocument('users', $profile->getId());
$audits
->setParam('userId', $profile->getId())
@ -2021,7 +2010,7 @@ App::put('/v1/account/recovery')
$usage
->setParam('users.update', 1)
;
$response->dynamic($recovery, Response::MODEL_TOKEN);
$response->dynamic($recoveryDocument, Response::MODEL_TOKEN);
});
App::post('/v1/account/verification')
@ -2085,9 +2074,12 @@ App::post('/v1/account/verification')
Authorization::setRole('user:' . $user->getId());
$user->setAttribute('tokens', $verification, Document::SET_TYPE_APPEND);
$verification = $dbForProject->createDocument('tokens', $verification
->setAttribute('$read', ['user:'.$user->getId()])
->setAttribute('$write', ['user:'.$user->getId()])
);
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
$dbForProject->deleteCachedDocument('users', $user->getId());
$url = Template::parseURL($url);
$url['query'] = Template::mergeQuery(((isset($url['query'])) ? $url['query'] : ''), ['userId' => $user->getId(), 'secret' => $verificationSecret, 'expire' => $expire]);
@ -2157,7 +2149,7 @@ App::put('/v1/account/verification')
/** @var Appwrite\Event\Event $audits */
/** @var Appwrite\Stats\Stats $usage */
$profile = $dbForProject->getDocument('users', $userId);
$profile = Authorization::skip(fn() => $dbForProject->getDocument('users', $userId));
if ($profile->isEmpty()) {
throw new Exception('User not found', 404, Exception::USER_NOT_FOUND);
@ -2173,19 +2165,15 @@ App::put('/v1/account/verification')
Authorization::setRole('user:' . $profile->getId());
$profile = $dbForProject->updateDocument('users', $profile->getId(), $profile->setAttribute('emailVerification', true));
$verificationDocument = $dbForProject->getDocument('tokens', $verification);
/**
* We act like we're updating and validating
* the verification token but actually we don't need it anymore.
*/
foreach ($tokens as $key => $token) {
if ($token->getId() === $verification) {
$verification = $token;
unset($tokens[$key]);
}
}
$dbForProject->updateDocument('users', $profile->getId(), $profile->setAttribute('tokens', $tokens));
$dbForProject->deleteDocument('tokens', $verification);
$dbForProject->deleteCachedDocument('users', $profile->getId());
$audits
->setParam('userId', $profile->getId())
@ -2196,5 +2184,5 @@ App::put('/v1/account/verification')
$usage
->setParam('users.update', 1)
;
$response->dynamic($verification, Response::MODEL_TOKEN);
$response->dynamic($verificationDocument, Response::MODEL_TOKEN);
});

View file

@ -1,5 +1,7 @@
<?php
use Appwrite\Extend\Exception;
use Appwrite\Network\Validator\URL;
use Appwrite\URL\URL as URLParse;
use Appwrite\Utopia\Response;
use chillerlan\QRCode\QRCode;
@ -8,17 +10,15 @@ use Utopia\App;
use Utopia\Cache\Adapter\Filesystem;
use Utopia\Cache\Cache;
use Utopia\Config\Config;
use Appwrite\Extend\Exception;
use Utopia\Database\Document;
use Utopia\Image\Image;
use Utopia\Validator\Boolean;
use Utopia\Validator\HexColor;
use Utopia\Validator\Range;
use Utopia\Validator\Text;
use Appwrite\Network\Validator\URL;
use Utopia\Validator\WhiteList;
$avatarCallback = function ($type, $code, $width, $height, $quality, $response) {
/** @var Appwrite\Utopia\Response $response */
$avatarCallback = function (string $type, string $code, int $width, int $height, int $quality, Response $response) {
$code = \strtolower($code);
$type = \strtolower($type);
@ -38,7 +38,7 @@ $avatarCallback = function ($type, $code, $width, $height, $quality, $response)
$output = 'png';
$date = \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT'; // 45 days cache
$key = \md5('/v1/avatars/'.$type.'/:code-' . $code . $width . $height . $quality . $output);
$key = \md5('/v1/avatars/' . $type . '/:code-' . $code . $width . $height . $quality . $output);
$path = $set[$code];
$type = 'png';
@ -56,8 +56,7 @@ $avatarCallback = function ($type, $code, $width, $height, $quality, $response)
->setContentType('image/png')
->addHeader('Expires', $date)
->addHeader('X-Appwrite-Cache', 'hit')
->send($data)
;
->send($data);
}
$image = new Image(\file_get_contents($path));
@ -95,7 +94,7 @@ App::get('/v1/avatars/credit-cards/:code')
->param('height', 100, new Range(0, 2000), 'Image height. Pass an integer between 0 to 2000. Defaults to 100.', true)
->param('quality', 100, new Range(0, 100), 'Image quality. Pass an integer between 0 to 100. Defaults to 100.', true)
->inject('response')
->action(fn($code, $width, $height, $quality, $response) => $avatarCallback('credit-cards', $code, $width, $height, $quality, $response));
->action(fn (string $code, int $width, int $height, int $quality, Response $response) => $avatarCallback('credit-cards', $code, $width, $height, $quality, $response));
App::get('/v1/avatars/browsers/:code')
->desc('Get Browser Icon')
@ -113,7 +112,7 @@ App::get('/v1/avatars/browsers/:code')
->param('height', 100, new Range(0, 2000), 'Image height. Pass an integer between 0 to 2000. Defaults to 100.', true)
->param('quality', 100, new Range(0, 100), 'Image quality. Pass an integer between 0 to 100. Defaults to 100.', true)
->inject('response')
->action(fn($code, $width, $height, $quality, $response) => $avatarCallback('browsers', $code, $width, $height, $quality, $response));
->action(fn (string $code, int $width, int $height, int $quality, Response $response) => $avatarCallback('browsers', $code, $width, $height, $quality, $response));
App::get('/v1/avatars/flags/:code')
->desc('Get Country Flag')
@ -131,7 +130,7 @@ App::get('/v1/avatars/flags/:code')
->param('height', 100, new Range(0, 2000), 'Image height. Pass an integer between 0 to 2000. Defaults to 100.', true)
->param('quality', 100, new Range(0, 100), 'Image quality. Pass an integer between 0 to 100. Defaults to 100.', true)
->inject('response')
->action(fn($code, $width, $height, $quality, $response) => $avatarCallback('flags', $code, $width, $height, $quality, $response));
->action(fn (string $code, int $width, int $height, int $quality, Response $response) => $avatarCallback('flags', $code, $width, $height, $quality, $response));
App::get('/v1/avatars/image')
->desc('Get Image from URL')
@ -145,11 +144,10 @@ App::get('/v1/avatars/image')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_IMAGE)
->param('url', '', new URL(['http', 'https']), 'Image URL which you want to crop.')
->param('width', 400, new Range(0, 2000), 'Resize preview image width, Pass an integer between 0 to 2000.', true)
->param('height', 400, new Range(0, 2000), 'Resize preview image height, Pass an integer between 0 to 2000.', true)
->param('width', 400, new Range(0, 2000), 'Resize preview image width, Pass an integer between 0 to 2000. Defaults to 400.', true)
->param('height', 400, new Range(0, 2000), 'Resize preview image height, Pass an integer between 0 to 2000. Defaults to 400.', true)
->inject('response')
->action(function ($url, $width, $height, $response) {
/** @var Appwrite\Utopia\Response $response */
->action(function (string $url, int $width, int $height, Response $response) {
$quality = 80;
$output = 'png';
@ -164,8 +162,7 @@ App::get('/v1/avatars/image')
->setContentType('image/png')
->addHeader('Expires', $date)
->addHeader('X-Appwrite-Cache', 'hit')
->send($data)
;
->send($data);
}
if (!\extension_loaded('imagick')) {
@ -180,7 +177,7 @@ App::get('/v1/avatars/image')
try {
$image = new Image($fetch);
} catch (\Exception$exception) {
} catch (\Exception $exception) {
throw new Exception('Unable to parse image', 500, Exception::GENERAL_SERVER_ERROR);
}
@ -196,8 +193,7 @@ App::get('/v1/avatars/image')
->setContentType('image/png')
->addHeader('Expires', $date)
->addHeader('X-Appwrite-Cache', 'miss')
->send($data);
;
->send($data);;
unset($image);
});
@ -215,8 +211,7 @@ App::get('/v1/avatars/favicon')
->label('sdk.response.type', Response::CONTENT_TYPE_IMAGE)
->param('url', '', new URL(['http', 'https']), 'Website URL which you want to fetch the favicon from.')
->inject('response')
->action(function ($url, $response) {
/** @var Appwrite\Utopia\Response $response */
->action(function (string $url, Response $response) {
$width = 56;
$height = 56;
@ -233,8 +228,7 @@ App::get('/v1/avatars/favicon')
->setContentType('image/png')
->addHeader('Expires', $date)
->addHeader('X-Appwrite-Cache', 'hit')
->send($data)
;
->send($data);
}
if (!\extension_loaded('imagick')) {
@ -248,7 +242,8 @@ App::get('/v1/avatars/favicon')
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_MAXREDIRS => 3,
CURLOPT_URL => $url,
CURLOPT_USERAGENT => \sprintf(APP_USERAGENT,
CURLOPT_USERAGENT => \sprintf(
APP_USERAGENT,
App::getEnv('_APP_VERSION', 'UNKNOWN'),
App::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS', APP_EMAIL_SECURITY)
),
@ -326,8 +321,7 @@ App::get('/v1/avatars/favicon')
->setContentType('image/x-icon')
->addHeader('Expires', $date)
->addHeader('X-Appwrite-Cache', 'miss')
->send($data)
;
->send($data);
}
$fetch = @\file_get_contents($outputHref, false);
@ -367,12 +361,11 @@ App::get('/v1/avatars/qr')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_IMAGE_PNG)
->param('text', '', new Text(512), 'Plain text to be converted to QR code image.')
->param('size', 400, new Range(0, 1000), 'QR code size. Pass an integer between 0 to 1000. Defaults to 400.', true)
->param('size', 400, new Range(1, 1000), 'QR code size. Pass an integer between 1 to 1000. Defaults to 400.', true)
->param('margin', 1, new Range(0, 10), 'Margin from edge. Pass an integer between 0 to 10. Defaults to 1.', true)
->param('download', false, new Boolean(true), 'Return resulting image with \'Content-Disposition: attachment \' headers for the browser to start downloading it. Pass 0 for no header, or 1 for otherwise. Default value is set to 0.', true)
->inject('response')
->action(function ($text, $size, $margin, $download, $response) {
/** @var Appwrite\Utopia\Response $response */
->action(function (string $text, int $size, int $margin, bool $download, Response $response) {
$download = ($download === '1' || $download === 'true' || $download === 1 || $download === true);
$options = new QROptions([
@ -394,8 +387,7 @@ App::get('/v1/avatars/qr')
$response
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache
->setContentType('image/png')
->send($image->output('png', 9))
;
->send($image->output('png', 9));
});
App::get('/v1/avatars/initials')
@ -416,9 +408,7 @@ App::get('/v1/avatars/initials')
->param('background', '', new HexColor(), 'Changes background color. By default a random color will be picked and stay will persistent to the given name.', true)
->inject('response')
->inject('user')
->action(function ($name, $width, $height, $color, $background, $response, $user) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Document $user */
->action(function (string $name, int $width, int $height, string $color, string $background, Response $response, Document $user) {
$themes = [
['color' => '#27005e', 'background' => '#e1d2f6'], // VIOLET
@ -438,8 +428,8 @@ App::get('/v1/avatars/initials')
$name = (!empty($name)) ? $name : $user->getAttribute('name', $user->getAttribute('email', ''));
$words = \explode(' ', \strtoupper($name));
// if there is no space, try to split by `_` underscore
$words = (count($words) == 1 ) ? \explode('_', \strtoupper($name)) : $words;
$words = (count($words) == 1) ? \explode('_', \strtoupper($name)) : $words;
$initials = null;
$code = 0;
@ -478,6 +468,5 @@ App::get('/v1/avatars/initials')
$response
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache
->setContentType('image/png')
->send($image->getImageBlob())
;
->send($image->getImageBlob());
});

View file

@ -19,7 +19,6 @@ use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\Permissions;
use Utopia\Database\Validator\QueryValidator;
use Utopia\Database\Validator\Queries as QueriesValidator;
use Utopia\Database\Validator\Structure;
use Utopia\Database\Validator\UID;
use Utopia\Database\Exception\Authorization as AuthorizationException;
@ -31,6 +30,8 @@ use Appwrite\Network\Validator\Email;
use Appwrite\Network\Validator\IP;
use Appwrite\Network\Validator\URL;
use Appwrite\Utopia\Database\Validator\CustomId;
use Appwrite\Utopia\Database\Validator\Queries as QueriesValidator;
use Appwrite\Utopia\Database\Validator\OrderAttributes as OrderAttributesValidator;
use Appwrite\Utopia\Response;
use Appwrite\Detector\Detector;
use Appwrite\Event\Event;
@ -150,7 +151,7 @@ App::post('/v1/database/collections')
->label('sdk.response.model', Response::MODEL_COLLECTION)
->param('collectionId', '', new CustomId(), 'Unique Id. Choose your own unique ID or pass the string "unique()" to auto generate it. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('name', '', new Text(128), 'Collection name. Max length: 128 chars.')
->param('permission', null, new WhiteList(['document', 'collection']), 'Permissions type model to use for reading documents in this collection. You can use collection-level permission set once on the collection using the `read` and `write` params, or you can set document-level permission where each document read and write params will decide who has access to read and write to each document individually. [learn more about permissions](https://appwrite.io/docs/permissions) and get a full list of available permissions.')
->param('permission', null, new WhiteList(['document', 'collection']), 'Specifies the permissions model used in this collection, which accepts either \'collection\' or \'document\'. For \'collection\' level permission, the permissions specified in read and write params are applied to all documents in the collection. For \'document\' level permissions, read and write permissions are specified in each document. [learn more about permissions](https://appwrite.io/docs/permissions) and get a full list of available permissions.')
->param('read', null, new Permissions(), 'An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions](https://appwrite.io/docs/permissions) and get a full list of available permissions.')
->param('write', null, new Permissions(), 'An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](https://appwrite.io/docs/permissions) and get a full list of available permissions.')
->inject('response')
@ -1742,6 +1743,13 @@ App::get('/v1/database/collections/:collectionId/documents')
}
}
if(!empty($orderAttributes)) {
$validator = new OrderAttributesValidator($collection->getAttribute('attributes', []), $collection->getAttribute('indexes', []), true);
if (!$validator->isValid($orderAttributes)) {
throw new Exception($validator->getDescription(), 400, Exception::GENERAL_QUERY_INVALID);
}
}
$cursorDocument = null;
if (!empty($cursor)) {
$cursorDocument = $collection->getAttribute('permission') === 'collection'

View file

@ -880,7 +880,7 @@ App::post('/v1/functions/:functionId/executions')
'trigger' => 'http', // http / schedule / event
'status' => 'waiting', // waiting / processing / completed / failed
'statusCode' => 0,
'stdout' => '',
'response' => '',
'stderr' => '',
'time' => 0.0,
'search' => implode(' ', [$functionId, $executionId]),
@ -956,7 +956,7 @@ App::post('/v1/functions/:functionId/executions')
/** Update execution status */
$execution->setAttribute('status', $executionResponse['status']);
$execution->setAttribute('statusCode', $executionResponse['statusCode']);
$execution->setAttribute('stdout', $executionResponse['stdout']);
$execution->setAttribute('response', $executionResponse['response']);
$execution->setAttribute('stderr', $executionResponse['stderr']);
$execution->setAttribute('time', $executionResponse['time']);
} catch (\Throwable $th) {

View file

@ -1,13 +1,15 @@
<?php
use Appwrite\Utopia\Response;
use Utopia\App;
use Appwrite\Extend\Exception;
use Utopia\Storage\Device\Local;
use Utopia\Storage\Storage;
use Appwrite\ClamAV\Network;
use Appwrite\Event\Event;
use Appwrite\Extend\Exception;
use Appwrite\Utopia\Response;
use Utopia\App;
use Utopia\Database\Document;
use Utopia\Registry\Registry;
use Utopia\Storage\Device;
use Utopia\Storage\Device\Local;
use Utopia\Storage\Storage;
App::get('/v1/health')
->desc('Get HTTP')
@ -21,8 +23,7 @@ App::get('/v1/health')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_HEALTH_STATUS)
->inject('response')
->action(function ($response) {
/** @var Appwrite\Utopia\Response $response */
->action(function (Response $response) {
$output = [
'status' => 'pass',
@ -40,8 +41,7 @@ App::get('/v1/health/version')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_HEALTH_VERSION)
->inject('response')
->action(function ($response) {
/** @var Appwrite\Utopia\Response $response */
->action(function (Response $response) {
$response->dynamic(new Document([ 'version' => APP_VERSION_STABLE ]), Response::MODEL_HEALTH_VERSION);
});
@ -59,9 +59,7 @@ App::get('/v1/health/db')
->label('sdk.response.model', Response::MODEL_HEALTH_STATUS)
->inject('response')
->inject('utopia')
->action(function ($response, $utopia) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\App $utopia */
->action(function (Response $response, App $utopia) {
$checkStart = \microtime(true);
@ -99,10 +97,7 @@ App::get('/v1/health/cache')
->label('sdk.response.model', Response::MODEL_HEALTH_STATUS)
->inject('response')
->inject('utopia')
->action(function ($response, $utopia) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\App $utopia */
/** @var Redis */
->action(function (Response $response, App $utopia) {
$checkStart = \microtime(true);
@ -132,8 +127,7 @@ App::get('/v1/health/time')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_HEALTH_TIME)
->inject('response')
->action(function ($response) {
/** @var Appwrite\Utopia\Response $response */
->action(function (Response $response) {
/*
* Code from: @see https://www.beliefmedia.com.au/query-ntp-time-server
@ -190,8 +184,7 @@ App::get('/v1/health/queue/webhooks')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE)
->inject('response')
->action(function ($response) {
/** @var Appwrite\Utopia\Response $response */
->action(function (Response $response) {
$response->dynamic(new Document([ 'size' => Resque::size(Event::WEBHOOK_QUEUE_NAME) ]), Response::MODEL_HEALTH_QUEUE);
}, ['response']);
@ -208,30 +201,11 @@ App::get('/v1/health/queue/logs')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE)
->inject('response')
->action(function ($response) {
/** @var Appwrite\Utopia\Response $response */
->action(function (Response $response) {
$response->dynamic(new Document([ 'size' => Resque::size(Event::AUDITS_QUEUE_NAME) ]), Response::MODEL_HEALTH_QUEUE);
}, ['response']);
App::get('/v1/health/queue/usage')
->desc('Get Usage Queue')
->groups(['api', 'health'])
->label('scope', 'health.read')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'health')
->label('sdk.method', 'getQueueUsage')
->label('sdk.description', '/docs/references/health/get-queue-usage.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE)
->inject('response')
->action(function ($response) {
/** @var Appwrite\Utopia\Response $response */
$response->dynamic(new Document([ 'size' => Resque::size(Event::USAGE_QUEUE_NAME) ]), Response::MODEL_HEALTH_QUEUE);
}, ['response']);
App::get('/v1/health/queue/certificates')
->desc('Get Certificates Queue')
->groups(['api', 'health'])
@ -244,8 +218,7 @@ App::get('/v1/health/queue/certificates')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE)
->inject('response')
->action(function ($response) {
/** @var Appwrite\Utopia\Response $response */
->action(function (Response $response) {
$response->dynamic(new Document([ 'size' => Resque::size(Event::CERTIFICATES_QUEUE_NAME) ]), Response::MODEL_HEALTH_QUEUE);
}, ['response']);
@ -262,8 +235,7 @@ App::get('/v1/health/queue/functions')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE)
->inject('response')
->action(function ($response) {
/** @var Appwrite\Utopia\Response $response */
->action(function (Response $response) {
$response->dynamic(new Document([ 'size' => Resque::size(Event::FUNCTIONS_QUEUE_NAME) ]), Response::MODEL_HEALTH_QUEUE);
}, ['response']);
@ -280,8 +252,7 @@ App::get('/v1/health/storage/local')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_HEALTH_STATUS)
->inject('response')
->action(function ($response) {
/** @var Appwrite\Utopia\Response $response */
->action(function (Response $response) {
$checkStart = \microtime(true);
@ -322,8 +293,7 @@ App::get('/v1/health/anti-virus')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_HEALTH_ANTIVIRUS)
->inject('response')
->action(function ($response) {
/** @var Appwrite\Utopia\Response $response */
->action(function (Response $response) {
$output = [
'status' => '',
@ -359,10 +329,7 @@ App::get('/v1/health/stats') // Currently only used internally
->inject('response')
->inject('register')
->inject('deviceFiles')
->action(function ($response, $register, $deviceFiles) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Registry\Registry $register */
/** @var Utopia\Storage\Device $deviceFiles */
->action(function (Response $response, Registry $register, Device $deviceFiles) {
$cache = $register->get('cache');

View file

@ -1,9 +1,12 @@
<?php
use Utopia\Database\Document;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Request;
use MaxMind\Db\Reader;
use Utopia\App;
use Utopia\Config\Config;
use Utopia\Database\Document;
use Utopia\Locale\Locale;
App::get('/v1/locale')
->desc('Get User Locale')
@ -20,12 +23,7 @@ App::get('/v1/locale')
->inject('response')
->inject('locale')
->inject('geodb')
->action(function ($request, $response, $locale, $geodb) {
/** @var Appwrite\Utopia\Request $request */
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Locale\Locale $locale */
/** @var MaxMind\Db\Reader $geodb */
->action(function (Request $request, Response $response, Locale $locale, Reader $geodb) {
$eu = Config::getParam('locale-eu');
$currencies = Config::getParam('locale-currencies');
$output = [];
@ -40,8 +38,8 @@ App::get('/v1/locale')
if ($record) {
$output['countryCode'] = $record['country']['iso_code'];
$output['country'] = $locale->getText('countries.'.strtolower($record['country']['iso_code']), $locale->getText('locale.country.unknown'));
$output['continent'] = $locale->getText('continents.'.strtolower($record['continent']['code']), $locale->getText('locale.country.unknown'));
$output['country'] = $locale->getText('countries.' . strtolower($record['country']['iso_code']), $locale->getText('locale.country.unknown'));
$output['continent'] = $locale->getText('continents.' . strtolower($record['continent']['code']), $locale->getText('locale.country.unknown'));
$output['continent'] = (isset($continents[$record['continent']['code']])) ? $continents[$record['continent']['code']] : $locale->getText('locale.country.unknown');
$output['continentCode'] = $record['continent']['code'];
$output['eu'] = (\in_array($record['country']['iso_code'], $eu)) ? true : false;
@ -63,8 +61,8 @@ App::get('/v1/locale')
}
$response
->addHeader('Cache-Control', 'public, max-age='.$time)
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + $time).' GMT') // 45 days cache
->addHeader('Cache-Control', 'public, max-age=' . $time)
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + $time) . ' GMT') // 45 days cache
;
$response->dynamic(new Document($output), Response::MODEL_LOCALE);
});
@ -82,16 +80,13 @@ App::get('/v1/locale/countries')
->label('sdk.response.model', Response::MODEL_COUNTRY_LIST)
->inject('response')
->inject('locale')
->action(function ($response, $locale) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Locale\Locale $locale */
->action(function (Response $response, Locale $locale) {
$list = Config::getParam('locale-countries'); /* @var $list array */
$output = [];
foreach ($list as $value) {
$output[] = new Document([
'name' => $locale->getText('countries.'.strtolower($value)),
'name' => $locale->getText('countries.' . strtolower($value)),
'code' => $value,
]);
}
@ -116,17 +111,14 @@ App::get('/v1/locale/countries/eu')
->label('sdk.response.model', Response::MODEL_COUNTRY_LIST)
->inject('response')
->inject('locale')
->action(function ($response, $locale) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Locale\Locale $locale */
->action(function (Response $response, Locale $locale) {
$eu = Config::getParam('locale-eu');
$output = [];
foreach ($eu as $code) {
if ($locale->getText('countries.'.strtolower($code), false) !== false) {
if ($locale->getText('countries.' . strtolower($code), false) !== false) {
$output[] = new Document([
'name' => $locale->getText('countries.'.strtolower($code)),
'name' => $locale->getText('countries.' . strtolower($code)),
'code' => $code,
]);
}
@ -152,21 +144,18 @@ App::get('/v1/locale/countries/phones')
->label('sdk.response.model', Response::MODEL_PHONE_LIST)
->inject('response')
->inject('locale')
->action(function ($response, $locale) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Locale\Locale $locale */
->action(function (Response $response, Locale $locale) {
$list = Config::getParam('locale-phones'); /* @var $list array */
$output = [];
\asort($list);
foreach ($list as $code => $name) {
if ($locale->getText('countries.'.strtolower($code), false) !== false) {
if ($locale->getText('countries.' . strtolower($code), false) !== false) {
$output[] = new Document([
'code' => '+'.$list[$code],
'code' => '+' . $list[$code],
'countryCode' => $code,
'countryName' => $locale->getText('countries.'.strtolower($code)),
'countryName' => $locale->getText('countries.' . strtolower($code)),
]);
}
}
@ -187,15 +176,12 @@ App::get('/v1/locale/continents')
->label('sdk.response.model', Response::MODEL_CONTINENT_LIST)
->inject('response')
->inject('locale')
->action(function ($response, $locale) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Locale\Locale $locale */
->action(function (Response $response, Locale $locale) {
$list = Config::getParam('locale-continents');
$list = Config::getParam('locale-continents'); /* @var $list array */
foreach ($list as $key => $value) {
foreach ($list as $value) {
$output[] = new Document([
'name' => $locale->getText('continents.'.strtolower($value)),
'name' => $locale->getText('continents.' . strtolower($value)),
'code' => $value,
]);
}
@ -219,12 +205,10 @@ App::get('/v1/locale/currencies')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_CURRENCY_LIST)
->inject('response')
->action(function ($response) {
/** @var Appwrite\Utopia\Response $response */
->action(function (Response $response) {
$list = Config::getParam('locale-currencies');
$list = array_map(fn($node) => new Document($node), $list);
$list = array_map(fn ($node) => new Document($node), $list);
$response->dynamic(new Document(['currencies' => $list, 'total' => \count($list)]), Response::MODEL_CURRENCY_LIST);
});
@ -242,12 +226,10 @@ App::get('/v1/locale/languages')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_LANGUAGE_LIST)
->inject('response')
->action(function ($response) {
/** @var Appwrite\Utopia\Response $response */
->action(function (Response $response) {
$list = Config::getParam('locale-languages');
$list = array_map(fn ($node) => new Document($node), $list);
$response->dynamic(new Document(['languages' => $list, 'total' => \count($list)]), Response::MODEL_LANGUAGE_LIST);
});
});

View file

@ -2,6 +2,7 @@
use Appwrite\Auth\Auth;
use Appwrite\Auth\Validator\Password;
use Appwrite\Event\Event;
use Appwrite\Network\Validator\CNAME;
use Appwrite\Network\Validator\Domain as DomainValidator;
use Appwrite\Network\Validator\Origin;
@ -18,6 +19,7 @@ use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\UID;
use Utopia\Domains\Domain;
use Utopia\Registry\Registry;
use Appwrite\Extend\Exception;
use Utopia\Validator\ArrayList;
use Utopia\Validator\Boolean;
@ -26,8 +28,7 @@ use Utopia\Validator\Range;
use Utopia\Validator\Text;
use Utopia\Validator\WhiteList;
App::init(function ($project) {
/** @var Utopia\Database\Document $project */
App::init(function (Document $project) {
if ($project->getId() !== 'console') {
throw new Exception('Access to this API is forbidden.', 401, Exception::GENERAL_ACCESS_FORBIDDEN);
@ -59,10 +60,7 @@ App::post('/v1/projects')
->inject('response')
->inject('dbForConsole')
->inject('dbForProject')
->action(function ($projectId, $name, $teamId, $description, $logo, $url, $legalName, $legalCountry, $legalState, $legalCity, $legalAddress, $legalTaxId, $response, $dbForConsole, $dbForProject) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForConsole */
/** @var Utopia\Database\Database $dbForProject */
->action(function (string $projectId, string $name, string $teamId, string $description, string $logo, string $url, string $legalName, string $legalCountry, string $legalState, string $legalCity, string $legalAddress, string $legalTaxId, Response $response, Database $dbForConsole, Database $dbForProject) {
$team = $dbForConsole->getDocument('teams', $teamId);
@ -77,6 +75,9 @@ App::post('/v1/projects')
}
$projectId = ($projectId == 'unique()') ? $dbForConsole->getId() : $projectId;
if($projectId === 'console') {
throw new Exception("'console' is a reserved project.", 400, Exception::PROJECT_RESERVED_PROJECT);
}
$project = $dbForConsole->createDocument('projects', new Document([
'$id' => $projectId == 'unique()' ? $dbForConsole->getId() : $projectId,
'$read' => ['team:' . $teamId],
@ -170,9 +171,7 @@ App::get('/v1/projects')
->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true)
->inject('response')
->inject('dbForConsole')
->action(function ($search, $limit, $offset, $cursor, $cursorDirection, $orderType, $response, $dbForConsole) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForConsole */
->action(function (string $search, int $limit, int $offset, string $cursor, string $cursorDirection, string $orderType, Response $response, Database $dbForConsole) {
if (!empty($cursor)) {
$cursorProject = $dbForConsole->getDocument('projects', $cursor);
@ -210,9 +209,7 @@ App::get('/v1/projects/:projectId')
->param('projectId', '', new UID(), 'Project unique ID.')
->inject('response')
->inject('dbForConsole')
->action(function ($projectId, $response, $dbForConsole) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForConsole */
->action(function (string $projectId, Response $response, Database $dbForConsole) {
$project = $dbForConsole->getDocument('projects', $projectId);
@ -239,11 +236,7 @@ App::get('/v1/projects/:projectId/usage')
->inject('dbForConsole')
->inject('dbForProject')
->inject('register')
->action(function ($projectId, $range, $response, $dbForConsole, $dbForProject, $register) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForConsole */
/** @var Utopia\Database\Database $dbForProject */
/** @var Utopia\Registry\Registry $register */
->action(function (string $projectId, string $range, Response $response, Database $dbForConsole, Database $dbForProject, Registry $register) {
$project = $dbForConsole->getDocument('projects', $projectId);
@ -360,9 +353,7 @@ App::patch('/v1/projects/:projectId')
->param('legalTaxId', '', new Text(256), 'Project legal tax ID. Max length: 256 chars.', true)
->inject('response')
->inject('dbForConsole')
->action(function ($projectId, $name, $description, $logo, $url, $legalName, $legalCountry, $legalState, $legalCity, $legalAddress, $legalTaxId, $response, $dbForConsole) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForConsole */
->action(function (string $projectId, string $name, string $description, string $logo, string $url, string $legalName, string $legalCountry, string $legalState, string $legalCity, string $legalAddress, string $legalTaxId, Response $response, Database $dbForConsole) {
$project = $dbForConsole->getDocument('projects', $projectId);
@ -402,10 +393,7 @@ App::patch('/v1/projects/:projectId/service')
->param('status', null, new Boolean(), 'Service status.')
->inject('response')
->inject('dbForConsole')
->action(function ($projectId, $service, $status, $response, $dbForConsole) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForConsole */
/** @var Boolean $status */
->action(function (string $projectId, string $service, bool $status, Response $response, Database $dbForConsole) {
$project = $dbForConsole->getDocument('projects', $projectId);
@ -437,9 +425,7 @@ App::patch('/v1/projects/:projectId/oauth2')
->param('secret', '', new text(512), 'Provider secret key. Max length: 512 chars.', true)
->inject('response')
->inject('dbForConsole')
->action(function ($projectId, $provider, $appId, $secret, $response, $dbForConsole) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForConsole */
->action(function (string $projectId, string $provider, string $appId, string $secret, Response $response, Database $dbForConsole) {
$project = $dbForConsole->getDocument('projects', $projectId);
@ -470,9 +456,7 @@ App::patch('/v1/projects/:projectId/auth/limit')
->param('limit', false, new Range(0, APP_LIMIT_USERS), 'Set the max number of users allowed in this project. Use 0 for unlimited.')
->inject('response')
->inject('dbForConsole')
->action(function ($projectId, $limit, $response, $dbForConsole) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForConsole */
->action(function (string $projectId, int $limit, Response $response, Database $dbForConsole) {
$project = $dbForConsole->getDocument('projects', $projectId);
@ -505,9 +489,7 @@ App::patch('/v1/projects/:projectId/auth/:method')
->param('status', false, new Boolean(true), 'Set the status of this auth method.')
->inject('response')
->inject('dbForConsole')
->action(function ($projectId, $method, $status, $response, $dbForConsole) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForConsole */
->action(function (string $projectId, string $method, bool $status, Response $response, Database $dbForConsole) {
$project = $dbForConsole->getDocument('projects', $projectId);
$auth = Config::getParam('auth')[$method] ?? [];
@ -541,11 +523,7 @@ App::delete('/v1/projects/:projectId')
->inject('user')
->inject('dbForConsole')
->inject('deletes')
->action(function ($projectId, $password, $response, $user, $dbForConsole, $deletes) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Document $user */
/** @var Utopia\Database\Database $dbForConsole */
/** @var Appwrite\Event\Event $deletes */
->action(function (string $projectId, string $password, Response $response, Document $user, Database $dbForConsole, Event $deletes) {
if (!Auth::passwordVerify($password, $user->getAttribute('password'))) { // Double check user password
throw new Exception('Invalid credentials', 401, Exception::USER_INVALID_CREDENTIALS);
@ -594,9 +572,7 @@ App::post('/v1/projects/:projectId/webhooks')
->param('httpPass', '', new Text(256), 'Webhook HTTP password. Max length: 256 chars.', true)
->inject('response')
->inject('dbForConsole')
->action(function ($projectId, $name, $events, $url, $security, $httpUser, $httpPass, $response, $dbForConsole) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForConsole */
->action(function (string $projectId, string $name, array $events, string $url, bool $security, string $httpUser, string $httpPass, Response $response, Database $dbForConsole) {
$project = $dbForConsole->getDocument('projects', $projectId);
@ -640,9 +616,7 @@ App::get('/v1/projects/:projectId/webhooks')
->param('projectId', '', new UID(), 'Project unique ID.')
->inject('response')
->inject('dbForConsole')
->action(function ($projectId, $response, $dbForConsole) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForConsole */
->action(function (string $projectId, Response $response, Database $dbForConsole) {
$project = $dbForConsole->getDocument('projects', $projectId);
@ -674,9 +648,7 @@ App::get('/v1/projects/:projectId/webhooks/:webhookId')
->param('webhookId', null, new UID(), 'Webhook unique ID.')
->inject('response')
->inject('dbForConsole')
->action(function ($projectId, $webhookId, $response, $dbForConsole) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForConsole */
->action(function (string $projectId, string $webhookId, Response $response, Database $dbForConsole) {
$project = $dbForConsole->getDocument('projects', $projectId);
@ -716,9 +688,7 @@ App::put('/v1/projects/:projectId/webhooks/:webhookId')
->param('httpPass', '', new Text(256), 'Webhook HTTP password. Max length: 256 chars.', true)
->inject('response')
->inject('dbForConsole')
->action(function ($projectId, $webhookId, $name, $events, $url, $security, $httpUser, $httpPass, $response, $dbForConsole) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForConsole */
->action(function (string $projectId, string $webhookId, string $name, array $events, string $url, bool $security, string $httpUser, string $httpPass, Response $response, Database $dbForConsole) {
$project = $dbForConsole->getDocument('projects', $projectId);
@ -766,9 +736,7 @@ App::delete('/v1/projects/:projectId/webhooks/:webhookId')
->param('webhookId', null, new UID(), 'Webhook unique ID.')
->inject('response')
->inject('dbForConsole')
->action(function ($projectId, $webhookId, $response, $dbForConsole) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForConsole */
->action(function (string $projectId, string $webhookId, Response $response, Database $dbForConsole) {
$project = $dbForConsole->getDocument('projects', $projectId);
@ -809,9 +777,7 @@ App::post('/v1/projects/:projectId/keys')
->param('scopes', null, new ArrayList(new WhiteList(array_keys(Config::getParam('scopes')), true)), 'Key scopes list.')
->inject('response')
->inject('dbForConsole')
->action(function ($projectId, $name, $scopes, $response, $dbForConsole) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForConsole */
->action(function (string $projectId, string $name, array $scopes, Response $response, Database $dbForConsole) {
$project = $dbForConsole->getDocument('projects', $projectId);
@ -850,9 +816,7 @@ App::get('/v1/projects/:projectId/keys')
->param('projectId', null, new UID(), 'Project unique ID.')
->inject('response')
->inject('dbForConsole')
->action(function ($projectId, $response, $dbForConsole) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForConsole */
->action(function (string $projectId, Response $response, Database $dbForConsole) {
$project = $dbForConsole->getDocument('projects', $projectId);
@ -884,9 +848,7 @@ App::get('/v1/projects/:projectId/keys/:keyId')
->param('keyId', null, new UID(), 'Key unique ID.')
->inject('response')
->inject('dbForConsole')
->action(function ($projectId, $keyId, $response, $dbForConsole) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForConsole */
->action(function (string $projectId, string $keyId, Response $response, Database $dbForConsole) {
$project = $dbForConsole->getDocument('projects', $projectId);
@ -922,9 +884,7 @@ App::put('/v1/projects/:projectId/keys/:keyId')
->param('scopes', null, new ArrayList(new WhiteList(array_keys(Config::getParam('scopes')), true)), 'Key scopes list')
->inject('response')
->inject('dbForConsole')
->action(function ($projectId, $keyId, $name, $scopes, $response, $dbForConsole) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForConsole */
->action(function (string $projectId, string $keyId, string $name, array $scopes, Response $response, Database $dbForConsole) {
$project = $dbForConsole->getDocument('projects', $projectId);
@ -966,9 +926,7 @@ App::delete('/v1/projects/:projectId/keys/:keyId')
->param('keyId', null, new UID(), 'Key unique ID.')
->inject('response')
->inject('dbForConsole')
->action(function ($projectId, $keyId, $response, $dbForConsole) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForConsole */
->action(function (string $projectId, string $keyId, Response $response, Database $dbForConsole) {
$project = $dbForConsole->getDocument('projects', $projectId);
@ -1012,9 +970,7 @@ App::post('/v1/projects/:projectId/platforms')
->param('hostname', '', new Text(256), 'Platform client hostname. Max length: 256 chars.', true)
->inject('response')
->inject('dbForConsole')
->action(function ($projectId, $type, $name, $key, $store, $hostname, $response, $dbForConsole) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForConsole */
->action(function (string $projectId, string $type, string $name, string $key, string $store, string $hostname, Response $response, Database $dbForConsole) {
$project = $dbForConsole->getDocument('projects', $projectId);
@ -1057,9 +1013,7 @@ App::get('/v1/projects/:projectId/platforms')
->param('projectId', '', new UID(), 'Project unique ID.')
->inject('response')
->inject('dbForConsole')
->action(function ($projectId, $response, $dbForConsole) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForConsole */
->action(function (string $projectId, Response $response, Database $dbForConsole) {
$project = $dbForConsole->getDocument('projects', $projectId);
@ -1091,9 +1045,7 @@ App::get('/v1/projects/:projectId/platforms/:platformId')
->param('platformId', null, new UID(), 'Platform unique ID.')
->inject('response')
->inject('dbForConsole')
->action(function ($projectId, $platformId, $response, $dbForConsole) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForConsole */
->action(function (string $projectId, string $platformId, Response $response, Database $dbForConsole) {
$project = $dbForConsole->getDocument('projects', $projectId);
@ -1131,9 +1083,7 @@ App::put('/v1/projects/:projectId/platforms/:platformId')
->param('hostname', '', new Text(256), 'Platform client URL. Max length: 256 chars.', true)
->inject('response')
->inject('dbForConsole')
->action(function ($projectId, $platformId, $name, $key, $store, $hostname, $response, $dbForConsole) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForConsole */
->action(function (string $projectId, string $platformId, string $name, string $key, string $store, string $hostname, Response $response, Database $dbForConsole) {
$project = $dbForConsole->getDocument('projects', $projectId);
@ -1178,9 +1128,7 @@ App::delete('/v1/projects/:projectId/platforms/:platformId')
->param('platformId', null, new UID(), 'Platform unique ID.')
->inject('response')
->inject('dbForConsole')
->action(function ($projectId, $platformId, $response, $dbForConsole) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForConsole */
->action(function (string $projectId, string $platformId, Response $response, Database $dbForConsole) {
$project = $dbForConsole->getDocument('projects', $projectId);
@ -1220,9 +1168,7 @@ App::post('/v1/projects/:projectId/domains')
->param('domain', null, new DomainValidator(), 'Domain name.')
->inject('response')
->inject('dbForConsole')
->action(function ($projectId, $domain, $response, $dbForConsole) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForConsole */
->action(function (string $projectId, string $domain, Response $response, Database $dbForConsole) {
$project = $dbForConsole->getDocument('projects', $projectId);
@ -1281,9 +1227,7 @@ App::get('/v1/projects/:projectId/domains')
->param('projectId', '', new UID(), 'Project unique ID.')
->inject('response')
->inject('dbForConsole')
->action(function ($projectId, $response, $dbForConsole) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForConsole */
->action(function (string $projectId, Response $response, Database $dbForConsole) {
$project = $dbForConsole->getDocument('projects', $projectId);
@ -1315,9 +1259,7 @@ App::get('/v1/projects/:projectId/domains/:domainId')
->param('domainId', null, new UID(), 'Domain unique ID.')
->inject('response')
->inject('dbForConsole')
->action(function ($projectId, $domainId, $response, $dbForConsole) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForConsole */
->action(function (string $projectId, string $domainId, Response $response, Database $dbForConsole) {
$project = $dbForConsole->getDocument('projects', $projectId);
@ -1351,9 +1293,7 @@ App::patch('/v1/projects/:projectId/domains/:domainId/verification')
->param('domainId', null, new UID(), 'Domain unique ID.')
->inject('response')
->inject('dbForConsole')
->action(function ($projectId, $domainId, $response, $dbForConsole) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForConsole */
->action(function (string $projectId, string $domainId, Response $response, Database $dbForConsole) {
$project = $dbForConsole->getDocument('projects', $projectId);
@ -1413,9 +1353,7 @@ App::delete('/v1/projects/:projectId/domains/:domainId')
->inject('response')
->inject('dbForConsole')
->inject('deletes')
->action(function ($projectId, $domainId, $response, $dbForConsole, $deletes) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForConsole */
->action(function (string $projectId, string $domainId, Response $response, Database $dbForConsole, $deletes) {
$project = $dbForConsole->getDocument('projects', $projectId);

View file

@ -2,8 +2,10 @@
use Appwrite\Auth\Auth;
use Appwrite\ClamAV\Network;
use Appwrite\Event\Event;
use Appwrite\Utopia\Database\Validator\CustomId;
use Appwrite\OpenSSL\OpenSSL;
use Appwrite\Stats\Stats;
use Appwrite\Utopia\Response;
use Utopia\App;
use Utopia\Cache\Adapter\Filesystem;
@ -21,6 +23,7 @@ use Utopia\Database\Validator\UID;
use Appwrite\Extend\Exception;
use Utopia\Image\Image;
use Utopia\Storage\Compression\Algorithms\GZIP;
use Utopia\Storage\Device;
use Utopia\Storage\Device\Local;
use Utopia\Storage\Storage;
use Utopia\Storage\Validator\File;
@ -34,6 +37,7 @@ use Utopia\Validator\Integer;
use Utopia\Validator\Range;
use Utopia\Validator\Text;
use Utopia\Validator\WhiteList;
use Utopia\Swoole\Request;
App::post('/v1/storage/buckets')
->desc('Create bucket')
@ -61,11 +65,7 @@ App::post('/v1/storage/buckets')
->inject('dbForProject')
->inject('audits')
->inject('usage')
->action(function ($bucketId, $name, $permission, $read, $write, $enabled, $maximumFileSize, $allowedFileExtensions, $encryption, $antivirus, $response, $dbForProject, $audits, $usage) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForProject */
/** @var Appwrite\Event\Event $audits */
/** @var Appwrite\Stats\Stats $usage */
->action(function (string $bucketId, string $name, string $permission, ?array $read, ?array $write, bool $enabled, int $maximumFileSize, array $allowedFileExtensions, bool $encryption, bool $antivirus, Response $response, Database $dbForProject, Event $audits, Stats $usage) {
$bucketId = $bucketId === 'unique()' ? $dbForProject->getId() : $bucketId;
try {
@ -157,12 +157,9 @@ App::get('/v1/storage/buckets')
->inject('response')
->inject('dbForProject')
->inject('usage')
->action(function ($search, $limit, $offset, $cursor, $cursorDirection, $orderType, $response, $dbForProject, $usage) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForProject */
/** @var Appwrite\Stats\Stats $usage */
->action(function (string $search, int $limit, int $offset, string $cursor, string $cursorDirection, string $orderType, Response $response, Database $dbForProject, Stats $usage) {
$queries = ($search) ? [new Query('name', Query::TYPE_SEARCH, $search)] : [];
$queries = ($search) ? [new Query('name', Query::TYPE_SEARCH, [$search])] : [];
if (!empty($cursor)) {
$cursorBucket = $dbForProject->getDocument('buckets', $cursor);
@ -195,10 +192,7 @@ App::get('/v1/storage/buckets/:bucketId')
->inject('response')
->inject('dbForProject')
->inject('usage')
->action(function ($bucketId, $response, $dbForProject, $usage) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForProject */
/** @var Appwrite\Stats\Stats $usage */
->action(function (string $bucketId, Response $response, Database $dbForProject, Stats $usage) {
$bucket = $dbForProject->getDocument('buckets', $bucketId);
@ -237,11 +231,7 @@ App::put('/v1/storage/buckets/:bucketId')
->inject('dbForProject')
->inject('audits')
->inject('usage')
->action(function ($bucketId, $name, $permission, $read, $write, $enabled, $maximumFileSize, $allowedFileExtensions, $encryption, $antivirus, $response, $dbForProject, $audits, $usage) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForProject */
/** @var Appwrite\Event\Event $audits */
/** @var Appwrite\Stats\Stats $usage */
->action(function (string $bucketId, string $name, string $permission, ?array $read, ?array $write, bool $enabled, ?int $maximumFileSize, array $allowedFileExtensions, bool $encryption, bool $antivirus, Response $response, Database $dbForProject, Event $audits, Stats $usage) {
$bucket = $dbForProject->getDocument('buckets', $bucketId);
@ -298,13 +288,7 @@ App::delete('/v1/storage/buckets/:bucketId')
->inject('deletes')
->inject('events')
->inject('usage')
->action(function ($bucketId, $response, $dbForProject, $audits, $deletes, $events, $usage) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForProject */
/** @var Appwrite\Event\Event $audits */
/** @var Appwrite\Event\Event $deletes */
/** @var Appwrite\Event\Event $events */
/** @var Appwrite\Stats\Stats $usage */
->action(function (string $bucketId, Response $response, Database $dbForProject, Event $audits, Event $deletes, Event $events, Stats $usage) {
$bucket = $dbForProject->getDocument('buckets', $bucketId);
@ -366,17 +350,7 @@ App::post('/v1/storage/buckets/:bucketId/files')
->inject('mode')
->inject('deviceFiles')
->inject('deviceLocal')
->action(function ($bucketId, $fileId, $file, $read, $write, $request, $response, $dbForProject, $user, $audits, $usage, $events, $mode, $deviceFiles, $deviceLocal) {
/** @var Utopia\Swoole\Request $request */
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForProject */
/** @var Utopia\Database\Document $user */
/** @var Appwrite\Event\Event $audits */
/** @var Appwrite\Event\Event $events */
/** @var Appwrite\Stats\Stats $usage */
/** @var Utopia\Storage\Device $deviceFiles */
/** @var Utopia\Storage\Device $deviceLocal */
/** @var string $mode */
->action(function (string $bucketId, string $fileId, array $file, ?array $read, ?array $write, Request $request, Response $response, Database $dbForProject, Document $user, Event $audits, Stats $usage, Event $events, string $mode, Device $deviceFiles, Device $deviceLocal) {
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
@ -711,11 +685,7 @@ App::get('/v1/storage/buckets/:bucketId/files')
->inject('dbForProject')
->inject('usage')
->inject('mode')
->action(function ($bucketId, $search, $limit, $offset, $cursor, $cursorDirection, $orderType, $response, $dbForProject, $usage, $mode) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForProject */
/** @var Appwrite\Stats\Stats $usage */
/** @var string $mode */
->action(function (string $bucketId, string $search, int $limit, int $offset, string $cursor, string $cursorDirection, string $orderType, Response $response, Database $dbForProject, Stats $usage, string $mode) {
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
@ -791,11 +761,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId')
->inject('dbForProject')
->inject('usage')
->inject('mode')
->action(function ($bucketId, $fileId, $response, $dbForProject, $usage, $mode) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForProject */
/** @var Appwrite\Stats\Stats $usage */
/** @var string $mode */
->action(function (string $bucketId, string $fileId, Response $response, Database $dbForProject, Stats $usage, string $mode) {
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
@ -863,15 +829,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
->inject('mode')
->inject('deviceFiles')
->inject('deviceLocal')
->action(function ($bucketId, $fileId, $width, $height, $gravity, $quality, $borderWidth, $borderColor, $borderRadius, $opacity, $rotation, $background, $output, $request, $response, $project, $dbForProject, $usage, $mode, $deviceFiles, $deviceLocal) {
/** @var Utopia\Swoole\Request $request */
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Document $project */
/** @var Utopia\Database\Database $dbForProject */
/** @var Appwrite\Stats\Stats $usage */
/** @var Utopia\Storage\Device $deviceFiles */
/** @var Utopia\Storage\Device $deviceLocal */
/** @var string $mode */
->action(function (string $bucketId, string $fileId, int $width, int $height, string $gravity, int $quality, int $borderWidth, string $borderColor, int $borderRadius, float $opacity, int $rotation, string $background, string $output, Request $request, Response $response, Document $project, Database $dbForProject, Stats $usage, string $mode, Device $deviceFiles, Device $deviceLocal) {
if (!\extension_loaded('imagick')) {
throw new Exception('Imagick extension is missing', 500, Exception::GENERAL_SERVER_ERROR);
@ -1041,13 +999,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download')
->inject('usage')
->inject('mode')
->inject('deviceFiles')
->action(function ($bucketId, $fileId, $request, $response, $dbForProject, $usage, $mode, $deviceFiles) {
/** @var Utopia\Swoole\Request $request */
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForProject */
/** @var Appwrite\Stats\Stats $usage */
/** @var Utopia\Storage\Device $deviceFiles */
/** @var string $mode */
->action(function (string $bucketId, string $fileId, Request $request, Response $response, Database $dbForProject, Stats $usage, string $mode, Device $deviceFiles) {
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
@ -1184,13 +1136,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view')
->inject('usage')
->inject('mode')
->inject('deviceFiles')
->action(function ($bucketId, $fileId, $response, $request, $dbForProject, $usage, $mode, $deviceFiles) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Swoole\Request $request */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Appwrite\Stats\Stats $usage */
/** @var Utopia\Storage\Device $deviceFiles */
/** @var string $mode */
->action(function (string $bucketId, string $fileId, Response $response, Request $request, Database $dbForProject, Stats $usage, string $mode, Device $deviceFiles) {
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
@ -1344,14 +1290,7 @@ App::put('/v1/storage/buckets/:bucketId/files/:fileId')
->inject('usage')
->inject('mode')
->inject('events')
->action(function ($bucketId, $fileId, $read, $write, $response, $dbForProject, $user, $audits, $usage, $mode, $events) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForProject */
/** @var Utopia\Database\Document $user */
/** @var Appwrite\Event\Event $audits */
/** @var Appwrite\Stats\Stats $usage */
/** @var Appwrite\Event\Event $events */
/** @var string $mode */
->action(function (string $bucketId, string $fileId, ?array $read, ?array $write,Response $response, Database $dbForProject, Document $user, Event $audits, Stats $usage, string $mode, Event $events) {
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
$read = (is_null($read) && !$user->isEmpty()) ? ['user:' . $user->getId()] : $read ?? []; // By default set read permissions for user
@ -1444,15 +1383,7 @@ App::delete('/v1/storage/buckets/:bucketId/files/:fileId')
->inject('mode')
->inject('deviceFiles')
->inject('project')
->action(function ($bucketId, $fileId, $response, $dbForProject, $events, $audits, $usage, $mode, $deviceFiles, $project) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForProject */
/** @var Utopia\Database\Database $dbForProject */
/** @var Appwrite\Event\Event $events */
/** @var Appwrite\Event\Event $audits */
/** @var Appwrite\Stats\Stats $usage */
/** @var Utopia\Storage\Device $deviceFiles */
/** @var string $mode */
->action(function (string $bucketId, string $fileId, Response $response, Database $dbForProject, Event $events, Event $audits, Stats $usage, string $mode, Device $deviceFiles, Document $project) {
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
@ -1539,9 +1470,7 @@ App::get('/v1/storage/usage')
->param('range', '30d', new WhiteList(['24h', '7d', '30d', '90d'], true), 'Date range.', true)
->inject('response')
->inject('dbForProject')
->action(function ($range, $response, $dbForProject) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForProject */
->action(function (string $range, Response $response, Database $dbForProject) {
$usage = [];
if (App::getEnv('_APP_USAGE_STATS', 'enabled') === 'enabled') {
@ -1651,9 +1580,7 @@ App::get('/v1/storage/:bucketId/usage')
->param('range', '30d', new WhiteList(['24h', '7d', '30d', '90d'], true), 'Date range.', true)
->inject('response')
->inject('dbForProject')
->action(function ($bucketId, $range, $response, $dbForProject) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForProject */
->action(function (string $bucketId, string $range, Response $response, Database $dbForProject) {
$bucket = $dbForProject->getDocument('buckets', $bucketId);

View file

@ -2,13 +2,17 @@
use Appwrite\Auth\Auth;
use Appwrite\Detector\Detector;
use Appwrite\Event\Event;
use Appwrite\Extend\Exception;
use Appwrite\Network\Validator\Email;
use Appwrite\Network\Validator\Host;
use Appwrite\Template\Template;
use Appwrite\Utopia\Database\Validator\CustomId;
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
use MaxMind\Db\Reader;
use Utopia\App;
use Appwrite\Extend\Exception;
use Utopia\Audit\Audit;
use Utopia\Config\Config;
use Utopia\Database\Database;
use Utopia\Database\Document;
@ -18,6 +22,7 @@ use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\UID;
use Utopia\Locale\Locale;
use Utopia\Validator\Text;
use Utopia\Validator\Range;
use Utopia\Validator\ArrayList;
@ -42,11 +47,8 @@ App::post('/v1/teams')
->inject('user')
->inject('dbForProject')
->inject('events')
->action(function ($teamId, $name, $roles, $response, $user, $dbForProject, $events) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Document $user */
/** @var Utopia\Database\Database $dbForProject */
/** @var Appwrite\Event\Event $events */
->inject('audits')
->action(function (string $teamId, string $name, array $roles, Response $response, Document $user, Database $dbForProject, Event $events, Event $audits) {
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
$isAppUser = Auth::isAppUser(Authorization::getRoles());
@ -79,16 +81,19 @@ App::post('/v1/teams')
]);
$membership = $dbForProject->createDocument('memberships', $membership);
// Attach user to team
$user->setAttribute('memberships', $membership, Document::SET_TYPE_APPEND);
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
$dbForProject->deleteCachedDocument('users', $user->getId());
}
if (!empty($user->getId())) {
$events->setParam('userId', $user->getId());
}
$audits
->setParam('event', 'teams.create')
->setParam('resource', 'team/'.$teamId)
->setParam('data', $team->getArrayCopy())
;
$response->setStatusCode(Response::STATUS_CODE_CREATED);
$response->dynamic($team, Response::MODEL_TEAM);
});
@ -112,9 +117,7 @@ App::get('/v1/teams')
->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true)
->inject('response')
->inject('dbForProject')
->action(function ($search, $limit, $offset, $cursor, $cursorDirection, $orderType, $response, $dbForProject) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForProject */
->action(function (string $search, int $limit, int $offset, string $cursor, string $cursorDirection, string $orderType, Response $response, Database $dbForProject) {
if (!empty($cursor)) {
$cursorTeam = $dbForProject->getDocument('teams', $cursor);
@ -153,9 +156,7 @@ App::get('/v1/teams/:teamId')
->param('teamId', '', new UID(), 'Team ID.')
->inject('response')
->inject('dbForProject')
->action(function ($teamId, $response, $dbForProject) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForProject */
->action(function (string $teamId, Response $response, Database $dbForProject) {
$team = $dbForProject->getDocument('teams', $teamId);
@ -182,9 +183,8 @@ App::put('/v1/teams/:teamId')
->param('name', null, new Text(128), 'New team name. Max length: 128 chars.')
->inject('response')
->inject('dbForProject')
->action(function ($teamId, $name, $response, $dbForProject) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForProject */
->inject('audits')
->action(function (string $teamId, string $name, Response $response, Database $dbForProject, Event $audits) {
$team = $dbForProject->getDocument('teams', $teamId);
@ -197,6 +197,12 @@ App::put('/v1/teams/:teamId')
->setAttribute('search', implode(' ', [$teamId, $name]))
);
$audits
->setParam('event', 'teams.update')
->setParam('resource', 'team/'.$teamId)
->setParam('data', $team->getArrayCopy())
;
$response->dynamic($team, Response::MODEL_TEAM);
});
@ -216,11 +222,8 @@ App::delete('/v1/teams/:teamId')
->inject('dbForProject')
->inject('events')
->inject('deletes')
->action(function ($teamId, $response, $dbForProject, $events, $deletes) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForProject */
/** @var Appwrite\Event\Event $events */
/** @var Appwrite\Event\Event $deletes */
->inject('audits')
->action(function (string $teamId, Response $response, Database $dbForProject, Event $events, Event $deletes, Event $audits) {
$team = $dbForProject->getDocument('teams', $teamId);
@ -252,6 +255,12 @@ App::delete('/v1/teams/:teamId')
->setParam('eventData', $response->output($team, Response::MODEL_TEAM))
;
$audits
->setParam('event', 'teams.delete')
->setParam('resource', 'team/'.$teamId)
->setParam('data', $team->getArrayCopy())
;
$response->noContent();
});
@ -281,13 +290,7 @@ App::post('/v1/teams/:teamId/memberships')
->inject('locale')
->inject('audits')
->inject('mails')
->action(function ($teamId, $email, $roles, $url, $name, $response, $project, $user, $dbForProject, $locale, $audits, $mails) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Document $project */
/** @var Utopia\Database\Document $user */
/** @var Utopia\Database\Database $dbForProject */
/** @var Appwrite\Event\Event $audits */
/** @var Appwrite\Event\Event $mails */
->action(function (string $teamId, string $email, array $roles, string $url, string $name, Response $response, Document $project, Document $user, Database $dbForProject, Locale $locale, Event $audits, Event $mails) {
if(empty(App::getEnv('_APP_SMTP_HOST'))) {
throw new Exception('SMTP Disabled', 503, Exception::GENERAL_SMTP_DISABLED);
@ -339,9 +342,10 @@ App::post('/v1/teams/:teamId/memberships')
'name' => $name,
'prefs' => new \stdClass(),
'sessions' => null,
'tokens' => [],
'memberships' => [],
'tokens' => null,
'memberships' => null,
'search' => implode(' ', [$userId, $email, $name]),
'deleted' => false
])));
} catch (Duplicate $th) {
throw new Exception('Account already exists', 409, Exception::USER_ALREADY_EXISTS);
@ -380,10 +384,7 @@ App::post('/v1/teams/:teamId/memberships')
$team->setAttribute('total', $team->getAttribute('total', 0) + 1);
$team = Authorization::skip(fn() => $dbForProject->updateDocument('teams', $team->getId(), $team));
// Attach user to team
$invitee->setAttribute('memberships', $membership, Document::SET_TYPE_APPEND);
$invitee = Authorization::skip(fn() => $dbForProject->updateDocument('users', $invitee->getId(), $invitee));
$dbForProject->deleteCachedDocument('users', $invitee->getId());
} else {
try {
$membership = $dbForProject->createDocument('memberships', $membership);
@ -445,9 +446,7 @@ App::get('/v1/teams/:teamId/memberships')
->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true)
->inject('response')
->inject('dbForProject')
->action(function ($teamId, $search, $limit, $offset, $cursor, $cursorDirection, $orderType, $response, $dbForProject) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForProject */
->action(function (string $teamId, string $search, int $limit, int $offset, string $cursor, string $cursorDirection, string $orderType, Response $response, Database $dbForProject) {
$team = $dbForProject->getDocument('teams', $teamId);
@ -519,9 +518,7 @@ App::get('/v1/teams/:teamId/memberships/:membershipId')
->param('membershipId', '', new UID(), 'Membership ID.')
->inject('response')
->inject('dbForProject')
->action(function ($teamId, $membershipId, $response, $dbForProject) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForProject */
->action(function (string $teamId, string $membershipId, Response $response, Database $dbForProject) {
$team = $dbForProject->getDocument('teams', $teamId);
@ -565,12 +562,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId')
->inject('user')
->inject('dbForProject')
->inject('audits')
->action(function ($teamId, $membershipId, $roles, $request, $response, $user, $dbForProject, $audits) {
/** @var Appwrite\Utopia\Request $request */
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Document $user */
/** @var Utopia\Database\Database $dbForProject */
/** @var Appwrite\Event\Event $audits */
->action(function (string $teamId, string $membershipId, array $roles, Request $request, Response $response, Document $user, Database $dbForProject, Event $audits) {
$team = $dbForProject->getDocument('teams', $teamId);
if ($team->isEmpty()) {
@ -604,13 +596,8 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId')
/**
* Replace membership on profile
*/
$memberships = array_filter($profile->getAttribute('memberships'), fn (Document $m) => $m->getId() !== $membership->getId());
$profile
->setAttribute('memberships', $memberships)
->setAttribute('memberships', $membership, Document::SET_TYPE_APPEND);
Authorization::skip(fn () => $dbForProject->updateDocument('users', $profile->getId(), $profile));
$dbForProject->deleteCachedDocument('users', $profile->getId());
$audits
->setParam('userId', $user->getId())
@ -647,13 +634,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
->inject('dbForProject')
->inject('geodb')
->inject('audits')
->action(function ($teamId, $membershipId, $userId, $secret, $request, $response, $user, $dbForProject, $geodb, $audits) {
/** @var Appwrite\Utopia\Request $request */
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Document $user */
/** @var Utopia\Database\Database $dbForProject */
/** @var MaxMind\Db\Reader $geodb */
/** @var Appwrite\Event\Event $audits */
->action(function (string $teamId, string $membershipId, string $userId, string $secret, Request $request, Response $response, Document $user, Database $dbForProject, Reader $geodb, Event $audits) {
$protocol = $request->getProtocol();
@ -700,7 +681,6 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
$user
->setAttribute('emailVerification', true)
->setAttribute('memberships', $membership, Document::SET_TYPE_APPEND)
;
// Log user in
@ -733,6 +713,8 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
Authorization::setRole('user:'.$userId);
$membership = $dbForProject->updateDocument('memberships', $membership->getId(), $membership);
$dbForProject->deleteCachedDocument('users', $user->getId());
$team = Authorization::skip(fn() => $dbForProject->updateDocument('teams', $team->getId(), $team->setAttribute('total', $team->getAttribute('total', 0) + 1)));
@ -776,11 +758,7 @@ App::delete('/v1/teams/:teamId/memberships/:membershipId')
->inject('dbForProject')
->inject('audits')
->inject('events')
->action(function ($teamId, $membershipId, $response, $dbForProject, $audits, $events) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForProject */
/** @var Appwrite\Event\Event $audits */
/** @var Appwrite\Event\Event $events */
->action(function (string $teamId, string $membershipId, Response $response, Database $dbForProject, Event $audits, Event $events) {
$membership = $dbForProject->getDocument('memberships', $membershipId);
@ -812,20 +790,7 @@ App::delete('/v1/teams/:teamId/memberships/:membershipId')
throw new Exception('Failed to remove membership from DB', 500, Exception::GENERAL_SERVER_ERROR);
}
$memberships = $user->getAttribute('memberships', []);
foreach ($memberships as $key => $child) {
/** @var Document $child */
if ($membershipId == $child->getId()) {
unset($memberships[$key]);
break;
}
}
$user->setAttribute('memberships', $memberships);
Authorization::skip(fn() => $dbForProject->updateDocument('users', $user->getId(), $user));
$dbForProject->deleteCachedDocument('users', $user->getId());
if ($membership->getAttribute('confirm')) { // Count only confirmed members
$team->setAttribute('total', \max($team->getAttribute('total', 0) - 1, 0));
@ -844,3 +809,88 @@ App::delete('/v1/teams/:teamId/memberships/:membershipId')
$response->noContent();
});
App::get('/v1/teams/:teamId/logs')
->desc('List Team Logs')
->groups(['api', 'teams'])
->label('scope', 'teams.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'teams')
->label('sdk.method', 'listLogs')
->label('sdk.description', '/docs/references/teams/get-team-logs.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_LOG_LIST)
->param('teamId', null, new UID(), 'Team ID.')
->param('limit', 25, new Range(0, 100), 'Maximum number of logs to return in response. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true)
->param('offset', 0, new Range(0, APP_LIMIT_COUNT), 'Offset value. The default value is 0. Use this value to manage pagination. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
->inject('response')
->inject('dbForProject')
->inject('locale')
->inject('geodb')
->action(function ($teamId, $limit, $offset, $response, $dbForProject, $locale, $geodb) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Document $project */
/** @var Utopia\Database\Database $dbForProject */
/** @var Utopia\Locale\Locale $locale */
/** @var MaxMind\Db\Reader $geodb */
$team = $dbForProject->getDocument('teams', $teamId);
if ($team->isEmpty()) {
throw new Exception('Team not found', 404, Exception::TEAM_NOT_FOUND);
}
$audit = new Audit($dbForProject);
$resource = 'team/'.$team->getId();
$logs = $audit->getLogsByResource($resource, $limit, $offset);
$output = [];
foreach ($logs as $i => &$log) {
$log['userAgent'] = (!empty($log['userAgent'])) ? $log['userAgent'] : 'UNKNOWN';
$detector = new Detector($log['userAgent']);
$detector->skipBotDetection(); // OPTIONAL: If called, bot detection will completely be skipped (bots will be detected as regular devices then)
$os = $detector->getOS();
$client = $detector->getClient();
$device = $detector->getDevice();
$output[$i] = new Document([
'event' => $log['event'],
'userId' => $log['userId'],
'userEmail' => $log['data']['userEmail'] ?? null,
'userName' => $log['data']['userName'] ?? null,
'mode' => $log['data']['mode'] ?? null,
'ip' => $log['ip'],
'time' => $log['time'],
'osCode' => $os['osCode'],
'osName' => $os['osName'],
'osVersion' => $os['osVersion'],
'clientType' => $client['clientType'],
'clientCode' => $client['clientCode'],
'clientName' => $client['clientName'],
'clientVersion' => $client['clientVersion'],
'clientEngine' => $client['clientEngine'],
'clientEngineVersion' => $client['clientEngineVersion'],
'deviceName' => $device['deviceName'],
'deviceBrand' => $device['deviceBrand'],
'deviceModel' => $device['deviceModel']
]);
$record = $geodb->get($log['ip']);
if ($record) {
$output[$i]['countryCode'] = $locale->getText('countries.'.strtolower($record['country']['iso_code']), false) ? \strtolower($record['country']['iso_code']) : '--';
$output[$i]['countryName'] = $locale->getText('countries.'.strtolower($record['country']['iso_code']), $locale->getText('locale.country.unknown'));
} else {
$output[$i]['countryCode'] = '--';
$output[$i]['countryName'] = $locale->getText('locale.country.unknown');
}
}
$response->dynamic(new Document([
'total' => $audit->countLogsByResource($resource),
'logs' => $output,
]), Response::MODEL_LOG_LIST);
});

View file

@ -64,8 +64,8 @@ App::post('/v1/users')
'name' => $name,
'prefs' => new \stdClass(),
'sessions' => null,
'tokens' => [],
'memberships' => [],
'tokens' => null,
'memberships' => null,
'search' => implode(' ', [$userId, $email, $name]),
'deleted' => false
]));
@ -453,7 +453,12 @@ App::patch('/v1/users/:userId/name')
throw new Exception('User not found', 404, Exception::USER_NOT_FOUND);
}
$user = $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('name', $name));
$user
->setAttribute('name', $name)
->setAttribute('search', \implode(' ', [$user->getId(), $user->getAttribute('email'), $name]));
;
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
$audits
->setParam('userId', $user->getId())
@ -542,8 +547,13 @@ App::patch('/v1/users/:userId/email')
$email = \strtolower($email);
$user
->setAttribute('email', $email)
->setAttribute('search', \implode(' ', [$user->getId(), $email, $user->getAttribute('name')]))
;
try {
$user = $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('email', $email));
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
} catch(Duplicate $th) {
throw new Exception('Email already exists', 409, Exception::USER_EMAIL_ALREADY_EXISTS);
}
@ -734,7 +744,7 @@ App::delete('/v1/users/:userId')
->setAttribute("email", null)
->setAttribute("password", null)
->setAttribute("deleted", true)
->setAttribute("tokens", [])
->setAttribute("tokens", null)
->setAttribute("search", null)
;

View file

@ -12,6 +12,7 @@ use Appwrite\Extend\Exception;
use Utopia\Config\Config;
use Utopia\Domains\Domain;
use Appwrite\Auth\Auth;
use Appwrite\Event\Event;
use Appwrite\Network\Validator\Origin;
use Appwrite\Utopia\Response\Filters\V11 as ResponseV11;
use Appwrite\Utopia\Response\Filters\V12 as ResponseV12;
@ -73,33 +74,44 @@ App::init(function ($utopia, $request, $response, $console, $project, $dbForCons
} else {
Authorization::disable();
$domainDocument = $dbForConsole->findOne('domains', [
new Query('domain', QUERY::TYPE_EQUAL, [$domain->get()])
]);
$mainDomain = null;
if(!empty(App::getEnv('_APP_DOMAIN', ''))) {
$mainDomain = App::getEnv('_APP_DOMAIN', '');
} else {
$domainDocument = $dbForConsole->findOne('domains', [], 0, ['_id'], ['ASC']);
$mainDomain = $domainDocument ? $domainDocument->getAttribute('domain') : $domain->get();
}
if (!$domainDocument) {
$domainDocument = new Document([
'domain' => $domain->get(),
'tld' => $domain->getSuffix(),
'registerable' => $domain->getRegisterable(),
'verification' => false,
'certificateId' => null,
if($mainDomain !== $domain->get()) {
Console::warning($domain->get() . ' is not a main domain. Skipping SSL certificate generation.');
} else {
$domainDocument = $dbForConsole->findOne('domains', [
new Query('domain', QUERY::TYPE_EQUAL, [$domain->get()])
]);
$domainDocument = $dbForConsole->createDocument('domains', $domainDocument);
Console::info('Issuing a TLS certificate for the master domain (' . $domain->get() . ') in a few seconds...');
Resque::enqueue('v1-certificates', 'CertificatesV1', [
'document' => $domainDocument,
'domain' => $domain->get(),
'validateTarget' => false,
'validateCNAME' => false,
]);
if (!$domainDocument) {
$domainDocument = new Document([
'domain' => $domain->get(),
'tld' => $domain->getSuffix(),
'registerable' => $domain->getRegisterable(),
'verification' => false,
'certificateId' => null,
]);
$domainDocument = $dbForConsole->createDocument('domains', $domainDocument);
Console::info('Issuing a TLS certificate for the main domain (' . $domain->get() . ') in a few seconds...');
Resque::enqueue(Event::CERTIFICATES_QUEUE_NAME, Event::CERTIFICATES_CLASS_NAME, [
'document' => $domainDocument,
'domain' => $domain->get(),
'validateTarget' => false,
'validateCNAME' => false,
]);
}
}
$domains[$domain->get()] = true;
Authorization::reset(); // ensure authorization is re-enabled
}
Config::setParam('domains', $domains);

View file

@ -1,90 +0,0 @@
CREATE DATABASE IF NOT EXISTS `appwrite` /*!40100 DEFAULT CHARACTER SET utf8mb4 */;
USE `appwrite`;
CREATE TABLE IF NOT EXISTS `template.abuse.abuse` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`_key` varchar(255) NOT NULL,
`_time` int(11) NOT NULL,
`_count` int(11) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
UNIQUE KEY `unique1` (`_key`,`_time`),
KEY `index1` (`_key`,`_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `template.audit.audit` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`userId` varchar(45) NOT NULL,
`event` varchar(45) NOT NULL,
`resource` varchar(45) DEFAULT NULL,
`userAgent` text NOT NULL,
`ip` varchar(45) NOT NULL,
`location` varchar(45) DEFAULT NULL,
`time` datetime NOT NULL,
`data` longtext DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `id_UNIQUE` (`id`),
KEY `index_1` (`userId`),
KEY `index_2` (`event`),
KEY `index_3` (`resource`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `template.database.documents` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'Unique ID for each node',
`uid` varchar(45) DEFAULT NULL,
`status` int(11) NOT NULL DEFAULT 0,
`createdAt` datetime DEFAULT NULL,
`updatedAt` datetime DEFAULT NULL,
`signature` varchar(32) NOT NULL,
`revision` varchar(45) NOT NULL,
`permissions` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `id_UNIQUE` (`id`),
UNIQUE KEY `index2` (`uid`),
KEY `index3` (`signature`,`uid`,`revision`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `template.database.properties` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'Primary Key',
`documentUid` varchar(45) NOT NULL COMMENT 'Unique UID foreign key',
`documentRevision` varchar(45) NOT NULL,
`key` varchar(32) NOT NULL COMMENT 'Property key name',
`value` text NOT NULL COMMENT 'Value of property',
`primitive` varchar(32) NOT NULL COMMENT 'Primitive type of property value',
`array` tinyint(4) NOT NULL DEFAULT 0,
`order` int(11) NOT NULL,
PRIMARY KEY (`id`),
KEY `index1` (`documentUid`),
KEY `index2` (`key`,`value`(5)),
FULLTEXT KEY `index3` (`value`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `template.database.relationships` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`revision` varchar(45) NOT NULL,
`start` varchar(45) NOT NULL COMMENT 'Unique UID foreign key',
`end` varchar(45) NOT NULL COMMENT 'Unique UID foreign key',
`key` varchar(256) NOT NULL,
`path` int(11) NOT NULL DEFAULT 0,
`array` tinyint(4) NOT NULL DEFAULT 0,
`order` int(11) NOT NULL DEFAULT 0,
PRIMARY KEY (`id`),
KEY `relationships_start_nodes_id_idx` (`start`),
KEY `relationships_end_nodes_id_idx` (`end`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `template.database.unique` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`key` varchar(128) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `index1` (`key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
/* Default App */
CREATE TABLE IF NOT EXISTS `app_console.database.documents` LIKE `template.database.documents`;
CREATE TABLE IF NOT EXISTS `app_console.database.properties` LIKE `template.database.properties`;
CREATE TABLE IF NOT EXISTS `app_console.database.relationships` LIKE `template.database.relationships`;
CREATE TABLE IF NOT EXISTS `app_console.database.unique` LIKE `template.database.unique`;
CREATE TABLE IF NOT EXISTS `app_console.audit.audit` LIKE `template.audit.audit`;
CREATE TABLE IF NOT EXISTS `app_console.abuse.abuse` LIKE `template.abuse.abuse`;

View file

@ -279,7 +279,7 @@ App::post('/v1/runtimes')
$endTime = \time();
$container = array_merge($container, [
'status' => 'ready',
'stdout' => \utf8_encode($stdout),
'response' => \utf8_encode($stdout),
'stderr' => \utf8_encode($stderr),
'startTime' => $startTime,
'endTime' => $endTime,
@ -408,7 +408,7 @@ App::post('/v1/execution')
->desc('Create an execution')
->param('runtimeId', '', new Text(64), 'The runtimeID to execute')
->param('vars', [], new Assoc(), 'Environment variables required for the build')
->param('data', '{}', new Text(8192), 'Data to be forwarded to the function, this is user specified.', true)
->param('data', '', new Text(8192), 'Data to be forwarded to the function, this is user specified.', true)
->param('timeout', 15, new Range(1, (int) App::getEnv('_APP_FUNCTIONS_TIMEOUT', 900)), 'Function maximum execution time in seconds.')
->inject('activeRuntimes')
->inject('response')
@ -512,7 +512,7 @@ App::post('/v1/execution')
$execution = [
'status' => $functionStatus,
'statusCode' => $statusCode,
'stdout' => \utf8_encode(\mb_substr($stdout, -16384)),
'response' => \utf8_encode(\mb_substr($stdout, -16384)),
'stderr' => \utf8_encode(\mb_substr($stderr, -16384)),
'time' => $executionTime,
];

View file

@ -181,7 +181,7 @@ if(!empty($user) || !empty($pass)) {
*/
Database::addFilter('casting',
function($value) {
return json_encode(['value' => $value]);
return json_encode(['value' => $value], JSON_PRESERVE_ZERO_FRACTION);
},
function($value) {
if (is_null($value)) {
@ -314,6 +314,30 @@ Database::addFilter('subQuerySessions',
}
);
Database::addFilter('subQueryTokens',
function($value) {
return null;
},
function($value, Document $document, Database $database) {
return Authorization::skip(fn() => $database
->find('tokens', [
new Query('userId', Query::TYPE_EQUAL, [$document->getId()])
], $database->getIndexLimit(), 0, []));
}
);
Database::addFilter('subQueryMemberships',
function($value) {
return null;
},
function($value, Document $document, Database $database) {
return Authorization::skip(fn() => $database
->find('memberships', [
new Query('userId', Query::TYPE_EQUAL, [$document->getId()])
], $database->getIndexLimit(), 0, []));
}
);
Database::addFilter('encrypt',
function($value) {
$key = App::getEnv('_APP_OPENSSL_KEY_V1');

View file

@ -163,28 +163,6 @@ $server->onStart(function () use ($stats, $register, $containerId, &$statsDocume
* Save current connections to the Database every 5 seconds.
*/
Timer::tick(5000, function () use ($register, $stats, &$statsDocument, $logError) {
/** @var Document $statsDocument */
foreach ($stats as $projectId => $value) {
$connections = $stats->get($projectId, 'connections') ?? 0;
$messages = $stats->get($projectId, 'messages' ?? 0);
$usage = new Event('v1-usage', 'UsageV1');
$usage
->setParam('projectId', $projectId)
->setParam('realtimeConnections', $connections)
->setParam('realtimeMessages', $messages)
->setParam('networkRequestSize', 0)
->setParam('networkResponseSize', 0);
$stats->set($projectId, [
'messages' => 0,
'connections' => 0
]);
if (App::getEnv('_APP_USAGE_STATS', 'enabled') == 'enabled') {
$usage->trigger();
}
}
$payload = [];
foreach ($stats as $projectId => $value) {
$payload[$projectId] = $stats->get($projectId, 'connectionsTotal');

View file

@ -418,7 +418,7 @@ $cli
// Get total storage
$dbForProject->setNamespace('_' . $projectId);
$storageTotal = $dbForProject->sum('deployments', 'size');
$deploymentsTotal = $dbForProject->sum('deployments', 'size');
$time = (int) (floor(time() / 1800) * 1800); // Time rounded to nearest 30 minutes
$id = \md5($time . '_30m_storage.deployments.total'); //Construct unique id for each metric using time, period and metric
@ -431,14 +431,14 @@ $cli
'period' => '30m',
'time' => $time,
'metric' => 'storage.deployments.total',
'value' => $storageTotal,
'value' => $deploymentsTotal,
'type' => 1,
]));
} else {
$dbForProject->updateDocument(
'stats',
$document->getId(),
$document->setAttribute('value', $storageTotal)
$document->setAttribute('value', $deploymentsTotal)
);
}
$time = (int) (floor(time() / 86400) * 86400); // Time rounded to nearest day
@ -450,14 +450,14 @@ $cli
'period' => '1d',
'time' => $time,
'metric' => 'storage.deployments.total',
'value' => $storageTotal,
'value' => $deploymentsTotal,
'type' => 1,
]));
} else {
$dbForProject->updateDocument(
'stats',
$document->getId(),
$document->setAttribute('value', $storageTotal)
$document->setAttribute('value', $deploymentsTotal)
);
}
} catch(\Exception $e) {
@ -714,7 +714,7 @@ $cli
}
/**
* Inserting project level sums for sub collections like storage.total
* Inserting project level sums for sub collections like storage.files.total
*/
foreach ($subCollectionTotals as $subCollection => $count) {
$dbForProject->setNamespace("_{$projectId}");
@ -754,6 +754,44 @@ $cli
$dbForProject->updateDocument('stats', $document->getId(),
$document->setAttribute('value', $count));
}
// aggregate storage.total = storage.files.total + storage.deployments.total
if($metricPrefix === 'storage' && $subCollection === 'files') {
$metric = 'storage.total';
$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 + $deploymentsTotal,
'type' => 1,
]));
} else {
$dbForProject->updateDocument('stats', $document->getId(),
$document->setAttribute('value', $count + $deploymentsTotal));
}
$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 + $deploymentsTotal,
'type' => 1,
]));
} else {
$dbForProject->updateDocument('stats', $document->getId(),
$document->setAttribute('value', $count + $deploymentsTotal));
}
}
}
} catch (\Exception$e) {
Console::warning("Failed to save database counters data for project {$collection}: {$e->getMessage()}");

View file

@ -621,7 +621,7 @@ $logs = $this->getParam('logs', null);
data-failure-param-alert-text="Failed to create attribute"
data-failure-param-alert-classname="error"
@reset="array = required = false"
x-data="{ array: false, required: false }">
x-data="{ array: false, required: false, size: null }">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="collectionId" data-ls-bind="{{router.params.id}}" />
@ -631,7 +631,7 @@ $logs = $this->getParam('logs', null);
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Allowed Characters A-Z, a-z, 0-9, and non-leading underscore, hyphen and dot</div>
<label for="string-length">Size</label>
<input id="string-length" name="size" type="number" class="margin-bottom" autocomplete="off" required value="255" data-cast-to="integer" />
<input id="string-length" name="size" type="number" class="margin-bottom" autocomplete="off" required value="255" data-cast-to="integer" x-model="size" />
<div class="margin-bottom">
<input x-model="required" name="required" class="button switch" type="checkbox" /> &nbsp; Required <span class="tooltip" data-tooltip="Mark whether this is a required attribute"><i class="icon-info-circled"></i></span>
@ -643,7 +643,7 @@ $logs = $this->getParam('logs', null);
<label for="xdefault">Default Value</label>
<template x-if="!(array || required)">
<input name="xdefault" type="text" class="margin-bottom-large" autocomplete="off">
<input name="xdefault" type="text" class="margin-bottom-large" autocomplete="off" :maxlength="size">
</template>
<template x-if="(array || required)">
<input name="xdefault" type="text" class="margin-bottom-large" autocomplete="off" disabled value="">
@ -677,7 +677,7 @@ $logs = $this->getParam('logs', null);
data-failure-param-alert-text="Failed to create attribute"
data-failure-param-alert-classname="error"
@reset="array = required = false"
x-data="{ array: false, required: false }">
x-data="{ array: false, required: false, min: null, max: null }">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="collectionId" data-ls-bind="{{router.params.id}}" />
@ -697,18 +697,18 @@ $logs = $this->getParam('logs', null);
<div class="row responsive thin">
<div class="col span-6 margin-bottom-small">
<label for="integer-min">Min</label>
<input id="integer-min" type="number" step="1" class="full-width" name="min" autocomplete="off" data-cast-to="integer" />
<input id="integer-min" type="number" step="1" class="full-width" name="min" autocomplete="off" data-cast-to="integer" x-model="min" />
</div>
<div class="col span-6 margin-bottom-small">
<label for="integer-max">Max</label>
<input id="integer-max" type="number" step="1" class="full-width" name="max" autocomplete="off" data-cast-to="integer" />
<input id="integer-max" type="number" step="1" class="full-width" name="max" autocomplete="off" data-cast-to="integer" x-model="max" />
</div>
</div>
<label for="integer-default">Default Value</label>
<template x-if="!(array || required)">
<input name="xdefault" type="number" class="margin-bottom-large" autocomplete="off" data-cast-to="integer">
<input name="xdefault" type="number" class="margin-bottom-large" autocomplete="off" data-cast-to="integer" :min="min" :max="max">
</template>
<template x-if="(array || required)">
<input name="xdefault" type="number" class="margin-bottom-large" autocomplete="off" data-cast-to="integer" disabled>
@ -742,7 +742,7 @@ $logs = $this->getParam('logs', null);
data-failure-param-alert-text="Failed to create attribute"
data-failure-param-alert-classname="error"
@reset="array = required = false"
x-data="{ array: false, required: false }">
x-data="{ array: false, required: false, min: null, max: null }">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="collectionId" data-ls-bind="{{router.params.id}}" />
@ -762,18 +762,18 @@ $logs = $this->getParam('logs', null);
<div class="row responsive thin">
<div class="col span-6 margin-bottom-small">
<label for="float-min">Min</label>
<input id="float-min" type="number" class="full-width" name="min" step="any" autocomplete="off" data-cast-to="float" />
<input id="float-min" type="number" class="full-width" name="min" step="any" autocomplete="off" data-cast-to="float" x-model="min" />
</div>
<div class="col span-6 margin-bottom-small">
<label for="float-max">Max</label>
<input id="float-max" type="number" class="full-width" name="max" step="any" autocomplete="off" data-cast-to="float" />
<input id="float-max" type="number" class="full-width" name="max" step="any" autocomplete="off" data-cast-to="float" x-model="max" />
</div>
</div>
<label for="float-default">Default Value</label>
<template x-if="!(array || required)">
<input name="xdefault" type="number" step="any" class="margin-bottom-large" autocomplete="off" data-cast-to="float">
<input name="xdefault" type="number" step="any" class="margin-bottom-large" autocomplete="off" data-cast-to="float" :min="min" :max="max">
</template>
<template x-if="(array || required)">
<input name="xdefault" type="number" step="any" class="margin-bottom-large" autocomplete="off" data-cast-to="float" disabled>
@ -1040,7 +1040,7 @@ $logs = $this->getParam('logs', null);
<div data-forms-clone="" data-label="Add Element" data-target="elements-section" data-first="1">
<div class="row responsive thin margin-bottom-tiny">
<div class="col span-11 margin-bottom-small">
<input type="text" class="full-width" name="elements" required autocomplete="off" maxlength="128" />
<input data-cast-to="array" type="text" class="full-width" name="elements" required autocomplete="off" maxlength="128" />
</div>
<div class="col span-1 margin-bottom-small">
<button type="button" data-remove class="dark danger small round pull-end" style="margin-top: 10px;"><i class="icon-cancel"></i></button>

View file

@ -55,17 +55,26 @@ $logs = $this->getParam('logs', null);
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Update Database Document"
data-service="{{|documentAction}}"
data-name="project-document"
data-scope="sdk"
data-event="submit"
data-success="trigger,redirect"
data-success-param-trigger-events="database.updateDocument"
data-success-param-redirect-url="/console/database/document?id={{serviceData.$id}}&collection={{project-collection.$id}}&project={{router.params.project}}"
data-failure="alert"
data-failure-param-alert-text="Failed to update document"
data-failure-param-alert-classname="error">
data-failure-param-alert-classname="error"
<?php if($new): ?>
data-analytics-label="Create Database Document"
data-success="trigger,redirect"
data-success-param-trigger-events="database.createDocument"
data-success-param-redirect-url="/console/database/collection?id={{project-collection.$id}}&project={{router.params.project}}"
data-failure-param-alert-text="Failed to create document"
<?php else: ?>
data-analytics-label="Update Database Document"
data-success="trigger,alert"
data-success-param-trigger-events="database.updateDocument"
data-success-param-alert-text="Your document was updated"
data-failure-param-alert-text="Failed to update document"
<?php endif; ?>
>
<input type="hidden" name="collectionId" data-ls-bind="{{project-collection.$id}}" />
<?php if(!$new): ?><input type="hidden" name="documentId" data-ls-bind="{{project-document.$id}}" /><?php endif; ?>
@ -104,6 +113,8 @@ $logs = $this->getParam('logs', null);
:placeholder="attr.default"
:name="attr.key"
:required="attr.required"
:min="attr.min"
:max="attr.max"
x-model="doc[attr.key]"
data-cast-to="integer" />
</template>
@ -114,6 +125,8 @@ $logs = $this->getParam('logs', null);
:placeholder="attr.default"
:name="attr.key"
:required="attr.required"
:min="attr.min"
:max="attr.max"
x-model="doc[attr.key]"
data-cast-to="float" />
</template>
@ -131,6 +144,7 @@ $logs = $this->getParam('logs', null);
:placeholder="attr.default"
:name="attr.key"
:required="attr.required"
:maxlength="attr.size"
x-model="doc[attr.key]"
data-cast-to="string"></textarea>
</template>
@ -197,6 +211,8 @@ $logs = $this->getParam('logs', null);
:placeholder="attr.default"
:name="attr.key"
:required="attr.required"
:min="attr.min"
:max="attr.max"
x-model="doc[attr.key][index]"
data-cast-to="integer" />
</template>
@ -207,6 +223,8 @@ $logs = $this->getParam('logs', null);
:placeholder="attr.default"
:name="attr.key"
:required="attr.required"
:min="attr.min"
:max="attr.max"
x-model="doc[attr.key][index]"
data-cast-to="float" />
</template>
@ -226,6 +244,7 @@ $logs = $this->getParam('logs', null);
:placeholder="attr.default"
:name="attr.key"
:required="attr.required"
:maxlength="attr.size"
x-model="doc[attr.key][index]"
data-cast-to="string"></textarea>
</template>

View file

@ -0,0 +1,12 @@
<?php
$provider = $this->getParam('provider', '');
?>
<label for="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Appid">Client ID<span class="tooltip" data-tooltip="Provided by Auth0"><i class="icon-info-circled"></i></span></label>
<input name="appId" id="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Appid" type="text" autocomplete="off" data-ls-bind="{{console-project.provider<?php echo $this->escape(ucfirst($provider)); ?>Appid}}" placeholder="Client ID" />
<label for="oauth2<?php echo $this->escape(ucfirst($provider)); ?>ClientSecret">Client Secret <span class="tooltip" data-tooltip="Provided in the Application you created in Auth0"><i class="icon-info-circled"></i></span></label>
<input name="clientSecret" id="oauth2<?php echo $this->escape(ucfirst($provider)); ?>ClientSecret" type="password" autocomplete="off" placeholder="Client Secret" />
<label for="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Domain">Auth0 Domain<span class="tooltip" data-tooltip="Your Auth0 Domain (without 'https://')"><i class="icon-info-circled"></i></span></label>
<input name="auth0Domain" id="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Domain" type="text" autocomplete="off" placeholder="YOUR_DOMAIN" />
<?php /*Hidden input for the final secret. Gets filled with a JSON via JS. */ ?>
<input name="secret" data-forms-oauth-custom="<?php echo $this->escape(ucfirst($provider)); ?>" id="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Secret" type="hidden" autocomplete="off" data-ls-bind="{{console-project.provider<?php echo $this->escape(ucfirst($provider)); ?>Secret}}" />

View file

@ -0,0 +1,14 @@
<?php
$provider = $this->getParam('provider', '');
?>
<label for="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Appid">Client ID<span class="tooltip" data-tooltip="Provided by Okta"><i class="icon-info-circled"></i></span></label>
<input name="appId" id="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Appid" type="text" autocomplete="off" data-ls-bind="{{console-project.provider<?php echo $this->escape(ucfirst($provider)); ?>Appid}}" placeholder="Client ID" />
<label for="oauth2<?php echo $this->escape(ucfirst($provider)); ?>ClientSecret">Client Secret <span class="tooltip" data-tooltip="Provided in the Application you created in Okta"><i class="icon-info-circled"></i></span></label>
<input name="clientSecret" id="oauth2<?php echo $this->escape(ucfirst($provider)); ?>ClientSecret" type="password" autocomplete="off" placeholder="Client Secret" />
<label for="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Domain">Okta Domain<span class="tooltip" data-tooltip="Your Okta Domain (without 'https://')"><i class="icon-info-circled"></i></span></label>
<input name="oktaDomain" id="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Domain" type="text" autocomplete="off" placeholder="dev-1337.okta.com" />
<label for="oauth2<?php echo $this->escape(ucfirst($provider)); ?>AuthorizationServerId">Authorization Server ID<span class="tooltip" data-tooltip="Authorization Server ID for custom authorization servers"><i class="icon-info-circled"></i></span></label>
<input name="authorizationServerId" id="oauth2<?php echo $this->escape(ucfirst($provider)); ?>AuthorizationServerId" type="text" autocomplete="off" placeholder="default" />
<?php /*Hidden input for the final secret. Gets filled with a JSON via JS. */ ?>
<input name="secret" data-forms-oauth-custom="<?php echo $this->escape(ucfirst($provider)); ?>" id="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Secret" type="hidden" autocomplete="off" data-ls-bind="{{console-project.provider<?php echo $this->escape(ucfirst($provider)); ?>Secret}}" />

View file

@ -233,7 +233,53 @@
</div>
</div>
</li>
<li data-state="/console/users/teams/team/audit?id={{router.params.id}}&project={{router.params.project}}">
<h2>Activity</h2>
<div
data-service="teams.listLogs"
data-name="logs"
data-param-team-id="{{router.params.id}}"
data-event="load,logs-load">
<div class="box margin-top margin-bottom" data-ls-if="{{logs.logs.length}} === 0" style="display: none" class="margin-top-xxl margin-bottom-xxl text-align-center">
<h3 class="text-bold margin-bottom-no">No logs available.</h3>
</div>
<div class="box" data-ls-if="{{logs.logs.length}} !== 0" style="display: none">
<table class="vertical small">
<thead>
<tr>
<th width="140">Date</th>
<th width="175">Event</th>
<th>Client</th>
<th width="90">Location</th>
<th width="90">IP</th>
</tr>
</thead>
<tbody data-ls-loop="logs.logs" data-ls-as="log">
<tr>
<td data-title="Date: "><span data-ls-bind="{{log.time|dateTime}}"></span></td>
<td data-title="Event: "><span data-ls-bind="{{log.event}}"></span></td>
<td data-title="Client: ">
<img onerror="this.onerror=null;this.className='avatar hide'" data-ls-if="{{log.clientCode|lowercase}} !== 'cli'" data-ls-attrs="src={{env.API}}/avatars/browsers/{{log.clientCode|lowercase}}?width=80&height=80&project={{env.PROJECT}},title={{log.clientName}},alt={{log.clientName}}" class="avatar xxs inline margin-end-small" />
<img onerror="this.onerror=null;this.className='avatar hide'" data-ls-if="{{log.clientCode|lowercase}} === 'cli'" data-ls-attrs="src=/images/clients/terminal.png?buster=<?php echo APP_CACHE_BUSTER; ?>,title={{log.clientName}},alt={{log.clientName}}" class="avatar xxs inline margin-end-small" />
<span data-ls-if="({{log.clientName}})" data-ls-bind="{{log.clientName}} {{log.clientVersion}} on {{log.model}} {{log.osName}} {{log.osVersion}}"></span>
<div data-ls-if="(!{{log.clientName}})" class="text-align-center text-fade">Unknown</div>
</td>
<td data-title="Location: ">
<img onerror="this.onerror=null;this.className='avatar hide'" data-ls-if="{{log.countryCode}} !== '--'" data-ls-attrs="src={{env.API}}/avatars/flags/{{log.countryCode}}?width=80&height=80&project={{env.PROJECT}}" class="avatar xxs inline margin-end-small" />
<span data-ls-bind="{{log.countryName}}"></span>
</td>
<td data-title="IP: "><span data-ls-bind="{{log.ip}}"></span></td>
</tr>
</tbody>
</table>
</div>
</div>
</li>
</ul>
</div>
</div>

View file

@ -242,6 +242,52 @@
<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>
<div data-ls-if="{{user.emailVerification}} === false" style="display: none">
<form name="users.updateVerification" class="margin-bottom"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Update Verification Status"
data-service="users.updateVerification"
data-scope="sdk"
data-event="submit"
data-success="trigger,alert"
data-success-param-alert-text="User verification status was updated successfully"
data-success-param-trigger-events="users.update"
data-failure="alert"
data-failure-param-alert-text="Failed to update user verification status"
data-failure-param-alert-classname="error"
>
<input type="hidden" disabled name="userId" data-ls-bind="{{user.$id}}">
<input type="hidden" disabled name="emailVerification" value="true" data-cast-to="boolean">
<button type="submit" class="dark fill">Verify Account</button>
</form>
</div>
<div data-ls-if="{{user.emailVerification}} === true" style="display: none">
<form name="users.updateVerification" class="margin-bottom"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Update Verification Status"
data-service="users.updateVerification"
data-scope="sdk"
data-event="submit"
data-success="trigger,alert"
data-success-param-alert-text="User verification status was updated successfully"
data-success-param-trigger-events="users.update"
data-failure="alert"
data-failure-param-alert-text="Failed to update user verification status"
data-failure-param-alert-classname="error"
>
<input type="hidden" disabled name="userId" data-ls-bind="{{user.$id}}">
<input type="hidden" disabled name="emailVerification" value="false" data-cast-to="boolean">
<button type="submit" class="dark fill">Unverify Account</button>
</form>
</div>
<div data-ls-if="{{user.status}} === true" style="display: none">
<form name="users.updateStatus" class="margin-bottom"
data-analytics

View file

@ -16,7 +16,7 @@ $image = $this->getParam('image', '');
services:
traefik:
image: traefik:2.5
image: traefik:2.7
container_name: appwrite-traefik
command:
- --providers.file.directory=/storage/config

View file

@ -71,6 +71,11 @@ class DatabaseV1 extends Worker
$dbForConsole = $this->getConsoleDB();
$dbForProject = $this->getProjectDB($projectId);
/**
* Fetch attribute from the database, since with Resque float values are loosing informations.
*/
$attribute = $dbForProject->getDocument('attributes', $attribute->getId());
$event = 'database.attributes.update';
$collectionId = $collection->getId();
$key = $attribute->getAttribute('key', '');

View file

@ -19,6 +19,7 @@ use Utopia\Audit\Audit;
require_once __DIR__ . '/../init.php';
Authorization::disable();
Authorization::setDefaultStatus(false);
Console::title('Deletes V1 Worker');
Console::success(APP_NAME . ' deletes worker v1 has started' . "\n");
@ -230,6 +231,11 @@ class DeletesV1 extends Worker
}
}
});
// Delete tokens
$this->deleteByGroup('tokens', [
new Query('userId', Query::TYPE_EQUAL, [$userId])
], $this->getProjectDB($projectId));
}
/**

View file

@ -254,7 +254,7 @@ class FunctionsV1 extends Worker
'trigger' => $trigger,
'status' => 'waiting',
'statusCode' => 0,
'stdout' => '',
'response' => '',
'stderr' => '',
'time' => 0.0,
'search' => implode(' ', [$functionId, $executionId]),
@ -303,7 +303,7 @@ class FunctionsV1 extends Worker
/** Update execution status */
$execution->setAttribute('status', $executionResponse['status']);
$execution->setAttribute('statusCode', $executionResponse['statusCode']);
$execution->setAttribute('stdout', $executionResponse['stdout']);
$execution->setAttribute('response', $executionResponse['response']);
$execution->setAttribute('stderr', $executionResponse['stderr']);
$execution->setAttribute('time', $executionResponse['time']);
} catch (\Throwable $th) {

View file

@ -56,7 +56,7 @@
"utopia-php/image": "0.5.*",
"utopia-php/orchestration": "0.4.*",
"resque/php-resque": "1.3.6",
"matomo/device-detector": "5.0.4",
"matomo/device-detector": "6.0.0",
"dragonmantank/cron-expression": "3.3.1",
"influxdb/influxdb-php": "1.15.2",
"phpmailer/phpmailer": "6.6.0",
@ -72,9 +72,9 @@
],
"require-dev": {
"appwrite/sdk-generator": "0.18.3",
"phpunit/phpunit": "9.5.10",
"swoole/ide-helper": "4.8.5",
"textalk/websocket": "1.5.5",
"phpunit/phpunit": "9.5.20",
"swoole/ide-helper": "4.8.9",
"textalk/websocket": "1.5.7",
"vimeo/psalm": "4.13.1"
},
"provide": {

70
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "e0de8d0bba618a46646cf3da1e06a652",
"content-hash": "78c5402d7bf745469d0063a9bc955df0",
"packages": [
{
"name": "adhocore/jwt",
@ -925,16 +925,16 @@
},
{
"name": "matomo/device-detector",
"version": "5.0.4",
"version": "6.0.0",
"source": {
"type": "git",
"url": "https://github.com/matomo-org/device-detector.git",
"reference": "99ea1953fc7f23f785e593ce1499a00586645530"
"reference": "7fc2af3af62bd69e6e3404d561e371a83c112be9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/matomo-org/device-detector/zipball/99ea1953fc7f23f785e593ce1499a00586645530",
"reference": "99ea1953fc7f23f785e593ce1499a00586645530",
"url": "https://api.github.com/repos/matomo-org/device-detector/zipball/7fc2af3af62bd69e6e3404d561e371a83c112be9",
"reference": "7fc2af3af62bd69e6e3404d561e371a83c112be9",
"shasum": ""
},
"require": {
@ -990,7 +990,7 @@
"source": "https://github.com/matomo-org/matomo",
"wiki": "https://dev.matomo.org/"
},
"time": "2022-02-18T19:51:56+00:00"
"time": "2022-04-11T09:58:17+00:00"
},
{
"name": "mongodb/mongodb",
@ -4549,16 +4549,16 @@
},
{
"name": "phpunit/phpunit",
"version": "9.5.10",
"version": "9.5.20",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "c814a05837f2edb0d1471d6e3f4ab3501ca3899a"
"reference": "12bc8879fb65aef2138b26fc633cb1e3620cffba"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c814a05837f2edb0d1471d6e3f4ab3501ca3899a",
"reference": "c814a05837f2edb0d1471d6e3f4ab3501ca3899a",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/12bc8879fb65aef2138b26fc633cb1e3620cffba",
"reference": "12bc8879fb65aef2138b26fc633cb1e3620cffba",
"shasum": ""
},
"require": {
@ -4574,7 +4574,7 @@
"phar-io/version": "^3.0.2",
"php": ">=7.3",
"phpspec/prophecy": "^1.12.1",
"phpunit/php-code-coverage": "^9.2.7",
"phpunit/php-code-coverage": "^9.2.13",
"phpunit/php-file-iterator": "^3.0.5",
"phpunit/php-invoker": "^3.1.1",
"phpunit/php-text-template": "^2.0.3",
@ -4588,7 +4588,7 @@
"sebastian/global-state": "^5.0.1",
"sebastian/object-enumerator": "^4.0.3",
"sebastian/resource-operations": "^3.0.3",
"sebastian/type": "^2.3.4",
"sebastian/type": "^3.0",
"sebastian/version": "^3.0.2"
},
"require-dev": {
@ -4636,11 +4636,11 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.10"
"source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.20"
},
"funding": [
{
"url": "https://phpunit.de/donate.html",
"url": "https://phpunit.de/sponsors.html",
"type": "custom"
},
{
@ -4648,7 +4648,7 @@
"type": "github"
}
],
"time": "2021-09-25T07:38:51+00:00"
"time": "2022-04-01T12:37:26+00:00"
},
{
"name": "psr/container",
@ -5560,28 +5560,28 @@
},
{
"name": "sebastian/type",
"version": "2.3.4",
"version": "3.0.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/type.git",
"reference": "b8cd8a1c753c90bc1a0f5372170e3e489136f914"
"reference": "b233b84bc4465aff7b57cf1c4bc75c86d00d6dad"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/type/zipball/b8cd8a1c753c90bc1a0f5372170e3e489136f914",
"reference": "b8cd8a1c753c90bc1a0f5372170e3e489136f914",
"url": "https://api.github.com/repos/sebastianbergmann/type/zipball/b233b84bc4465aff7b57cf1c4bc75c86d00d6dad",
"reference": "b233b84bc4465aff7b57cf1c4bc75c86d00d6dad",
"shasum": ""
},
"require": {
"php": ">=7.3"
},
"require-dev": {
"phpunit/phpunit": "^9.3"
"phpunit/phpunit": "^9.5"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.3-dev"
"dev-master": "3.0-dev"
}
},
"autoload": {
@ -5604,7 +5604,7 @@
"homepage": "https://github.com/sebastianbergmann/type",
"support": {
"issues": "https://github.com/sebastianbergmann/type/issues",
"source": "https://github.com/sebastianbergmann/type/tree/2.3.4"
"source": "https://github.com/sebastianbergmann/type/tree/3.0.0"
},
"funding": [
{
@ -5612,7 +5612,7 @@
"type": "github"
}
],
"time": "2021-06-15T12:49:02+00:00"
"time": "2022-03-15T09:54:48+00:00"
},
{
"name": "sebastian/version",
@ -5669,16 +5669,16 @@
},
{
"name": "swoole/ide-helper",
"version": "4.8.5",
"version": "4.8.9",
"source": {
"type": "git",
"url": "https://github.com/swoole/ide-helper.git",
"reference": "d03c707d4dc803228e93b4884c72949c4d28e8b8"
"reference": "8f82ba3b6af04a5bccb97c1654af992d1ee8b0fe"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/swoole/ide-helper/zipball/d03c707d4dc803228e93b4884c72949c4d28e8b8",
"reference": "d03c707d4dc803228e93b4884c72949c4d28e8b8",
"url": "https://api.github.com/repos/swoole/ide-helper/zipball/8f82ba3b6af04a5bccb97c1654af992d1ee8b0fe",
"reference": "8f82ba3b6af04a5bccb97c1654af992d1ee8b0fe",
"shasum": ""
},
"type": "library",
@ -5695,7 +5695,7 @@
"description": "IDE help files for Swoole.",
"support": {
"issues": "https://github.com/swoole/ide-helper/issues",
"source": "https://github.com/swoole/ide-helper/tree/4.8.5"
"source": "https://github.com/swoole/ide-helper/tree/4.8.9"
},
"funding": [
{
@ -5707,7 +5707,7 @@
"type": "github"
}
],
"time": "2021-12-24T22:44:20+00:00"
"time": "2022-04-18T20:38:04+00:00"
},
{
"name": "symfony/console",
@ -6221,16 +6221,16 @@
},
{
"name": "textalk/websocket",
"version": "1.5.5",
"version": "1.5.7",
"source": {
"type": "git",
"url": "https://github.com/Textalk/websocket-php.git",
"reference": "846542f82658132cd36acb7a7e8ce0f03960c295"
"reference": "1712325e99b6bf869ccbf9bf41ab749e7328ea46"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Textalk/websocket-php/zipball/846542f82658132cd36acb7a7e8ce0f03960c295",
"reference": "846542f82658132cd36acb7a7e8ce0f03960c295",
"url": "https://api.github.com/repos/Textalk/websocket-php/zipball/1712325e99b6bf869ccbf9bf41ab749e7328ea46",
"reference": "1712325e99b6bf869ccbf9bf41ab749e7328ea46",
"shasum": ""
},
"require": {
@ -6264,9 +6264,9 @@
"description": "WebSocket client and server",
"support": {
"issues": "https://github.com/Textalk/websocket-php/issues",
"source": "https://github.com/Textalk/websocket-php/tree/1.5.5"
"source": "https://github.com/Textalk/websocket-php/tree/1.5.7"
},
"time": "2021-08-07T10:21:40+00:00"
"time": "2022-03-29T09:46:59+00:00"
},
{
"name": "theseer/tokenizer",

View file

@ -14,7 +14,7 @@ version: '3'
services:
traefik:
image: traefik:2.5
image: traefik:2.7
<<: *x-logging
container_name: appwrite-traefik
command:

View file

@ -1 +1,3 @@
You can use this endpoint to show different browser icons to your users. The code argument receives the browser code as it appears in your user /account/sessions endpoint. Use width, height and quality arguments to change the output settings.
You can use this endpoint to show different browser icons to your users. The code argument receives the browser code as it appears in your user [GET /account/sessions](/docs/client/account#accountGetSessions) endpoint. Use width, height and quality arguments to change the output settings.
When one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.

View file

@ -1 +1,3 @@
The credit card endpoint will return you the icon of the credit card provider you need. Use width, height and quality arguments to change the output settings.
The credit card endpoint will return you the icon of the credit card provider you need. Use width, height and quality arguments to change the output settings.
When one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.

View file

@ -1 +1,3 @@
You can use this endpoint to show different country flags icons to your users. The code argument receives the 2 letter country code. Use width, height and quality arguments to change the output settings.
You can use this endpoint to show different country flags icons to your users. The code argument receives the 2 letter country code. Use width, height and quality arguments to change the output settings.
When one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.

View file

@ -1 +1,3 @@
Use this endpoint to fetch a remote image URL and crop it to any image size you want. This endpoint is very useful if you need to crop and display remote images in your app or in case you want to make sure a 3rd party image is properly served using a TLS protocol.
Use this endpoint to fetch a remote image URL and crop it to any image size you want. This endpoint is very useful if you need to crop and display remote images in your app or in case you want to make sure a 3rd party image is properly served using a TLS protocol.
When one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 400x400px.

View file

@ -1,3 +1,5 @@
Use this endpoint to show your user initials avatar icon on your website or app. By default, this route will try to print your logged-in user name or email initials. You can also overwrite the user name if you pass the 'name' parameter. If no name is given and no user is logged, an empty avatar will be returned.
You can use the color and background params to change the avatar colors. By default, a random theme will be selected. The random theme will persist for the user's initials when reloading the same theme will always return for the same initials.
You can use the color and background params to change the avatar colors. By default, a random theme will be selected. The random theme will persist for the user's initials when reloading the same theme will always return for the same initials.
When one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.

View file

@ -1 +1 @@
Converts a given plain text to a QR code image. You can use the query parameters to change the size and style of the resulting image.
Converts a given plain text to a QR code image. You can use the query parameters to change the size and style of the resulting image.

View file

@ -1 +0,0 @@
Get the number of usage stats that are waiting to be processed in the Appwrite internal queue server.

View file

@ -0,0 +1 @@
Get the team activity logs list by its unique ID.

View file

@ -1,7 +1,7 @@
## Getting Started
### Init your SDK
Initialize your SDK with your Appwrite server API endpoint and project ID which can be found in your project settings page and your new API secret Key from project's API keys section.
Initialize your SDK with your Appwrite server API endpoint and project ID which can be found on your project settings page and your new API secret Key from project's API keys section.
```python
from appwrite.client import Client

14
package-lock.json generated
View file

@ -12,7 +12,7 @@
"chart.js": "^3.7.1",
"markdown-it": "^12.3.2",
"pell": "^1.0.6",
"prismjs": "^1.27.0",
"prismjs": "^1.28.0",
"turndown": "^7.1.1"
},
"devDependencies": {
@ -3566,9 +3566,9 @@
}
},
"node_modules/prismjs": {
"version": "1.27.0",
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.27.0.tgz",
"integrity": "sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==",
"version": "1.28.0",
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.28.0.tgz",
"integrity": "sha512-8aaXdYvl1F7iC7Xm1spqSaY/OJBpYW3v+KJ+F17iYxvdc8sfjW194COK5wVhMZX45tGteiBQgdvD/nhxcRwylw==",
"engines": {
"node": ">=6"
}
@ -7981,9 +7981,9 @@
"dev": true
},
"prismjs": {
"version": "1.27.0",
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.27.0.tgz",
"integrity": "sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA=="
"version": "1.28.0",
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.28.0.tgz",
"integrity": "sha512-8aaXdYvl1F7iC7Xm1spqSaY/OJBpYW3v+KJ+F17iYxvdc8sfjW194COK5wVhMZX45tGteiBQgdvD/nhxcRwylw=="
},
"process-nextick-args": {
"version": "2.0.1",

View file

@ -20,7 +20,7 @@
"chart.js": "^3.7.1",
"markdown-it": "^12.3.2",
"pell": "^1.0.6",
"prismjs": "^1.27.0",
"prismjs": "^1.28.0",
"turndown": "^7.1.1"
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -696,7 +696,7 @@ if(this.getFile(id)){this.updateFile(id,{name:file.name,completed:false,failed:f
target.reset();try{const response=await sdk.storage.createFile(bucketId,fileId,file,read,write,(progress)=>{this.updateFile(id,{id:progress.$id,progress:Math.round(progress.progress),error:"",});id=progress.$id;const file=this.getFile(id)??{};if(file.cancelled===true){throw'USER_CANCELLED';}});const existingFile=this.getFile(id)??{};if(existingFile.cancelled){this.updateFile(id,{id:response.$id,name:response.name,failed:false,});id=response.$id;throw'USER_CANCELLED'}else{this.updateFile(id,{id:response.$id,name:response.name,progress:100,completed:true,failed:false,});id=response.$id;}
document.dispatchEvent(new CustomEvent('storage.createFile'));}catch(error){if(error==='USER_CANCELLED'){await sdk.storage.deleteFile(bucketId,id);this.updateFile(id,{cancelled:false,failed:true,});this.removeFile(id);}else{this.updateFile(id,{id:id,failed:true,error:error.message??error});}
document.dispatchEvent(new CustomEvent('storage.createFile'));}}});});})(window);(function(window){"use strict";window.ls.view.add({selector:"data-service",controller:function(element,view,container,form,alerts,expression,window){let action=element.dataset["service"];let service=element.dataset["name"]||null;let event=expression.parse(element.dataset["event"]);let confirm=element.dataset["confirm"]||"";let loading=element.dataset["loading"]||"";let loaderId=null;let scope=element.dataset["scope"]||"sdk";let success=element.dataset["success"]||"";let failure=element.dataset["failure"]||"";let running=false;let callbacks={hide:function(){return function(){return element.style.opacity='0';};},reset:function(){return function(){if("FORM"===element.tagName){return element.reset();}
throw new Error("This callback is only valid for forms");};},alert:function(text,classname){return function(alerts){alerts.add({text:text,class:classname||"success"},3000);};},redirect:function(url){return function(router){if(url==="/console"){window.location=url;return;}
throw new Error("This callback is only valid for forms");};},alert:function(text,classname){return function(alerts){alerts.add({text:text,class:classname||"success"},6000);};},redirect:function(url){return function(router){if(url==="/console"){window.location=url;return;}
router.change(url||"/");};},reload:function(){return function(router){router.reload();};},state:function(keys){let updateQueryString=function(key,value,url){var re=new RegExp("([?&])"+key+"=.*?(&|#|$)(.*)","gi"),hash;if(re.test(url)){if(typeof value!=="undefined"&&value!==null){return url.replace(re,"$1"+key+"="+value+"$2$3");}else{hash=url.split("#");url=hash[0].replace(re,"$1$3").replace(/(&|\?)$/,"");if(typeof hash[1]!=="undefined"&&hash[1]!==null){url+="#"+hash[1];}
return url;}}else{if(typeof value!=="undefined"&&value!==null){var separator=url.indexOf("?")!==-1?"&":"?";hash=url.split("#");url=hash[0]+separator+key+"="+value;if(typeof hash[1]!=="undefined"&&hash[1]!==null){url+="#"+hash[1];}
return url;}else{return url;}}};keys=keys.split(",").map(element=>element.trim());return function(serviceForm,router,window){let url=window.location.href;keys.map(node=>{node=node.split("=");let key=node[0]||"";let name=node[1]||key;let value=getValue(key,"param",serviceForm);url=updateQueryString(name,value?value:null,url);});if(url!==window.location.href){window.history.pushState({},"",url);router.reset();}};},trigger:function(events){return function(document){events=events.trim().split(",");for(let i=0;i<events.length;i++){if(""===events[i]){continue;}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 208 KiB

After

Width:  |  Height:  |  Size: 507 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 250 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

View file

@ -48,7 +48,7 @@
mode: '',
};
this.headers = {
'x-sdk-version': 'appwrite:web:4.0.4',
'x-sdk-version': 'appwrite:web:5.0.0',
'X-Appwrite-Response-Format': '0.13.0',
};
this.realtime = {
@ -371,7 +371,7 @@
*
* Update currently logged in user password. For validation, user is required
* to pass in the new password, and the old password. For users created with
* OAuth and Team Invites, oldPassword is optional.
* OAuth, Team Invites and Magic URL, oldPassword is optional.
*
* @param {string} password
* @param {string} oldPassword
@ -768,6 +768,9 @@
/**
* Update Session (Refresh Tokens)
*
* Access tokens have limited lifespan and expire to mitigate security risks.
* If session was created using an OAuth provider, this route can be used to
* "refresh" the access token.
*
* @param {string} sessionId
* @throws {AppwriteException}
@ -1923,9 +1926,7 @@
/**
* Delete Document
*
* Delete a document by its unique ID. This endpoint deletes only the parent
* documents, its attributes and relations to other documents. Child documents
* **will not** be deleted.
* Delete a document by its unique ID.
*
* @param {string} collectionId
* @param {string} documentId
@ -2263,7 +2264,7 @@
}, payload);
}),
/**
* List the currently active function runtimes.
* List runtimes
*
* Get a list of all runtimes that are currently active on your instance.
*
@ -2488,8 +2489,8 @@
if (onProgress) {
onProgress({
$id: response.$id,
progress: Math.min((counter + 1) * Appwrite.CHUNK_SIZE, size) / size * 100,
sizeUploaded: end + 1,
progress: Math.min((counter + 1) * Appwrite.CHUNK_SIZE - 1, size) / size * 100,
sizeUploaded: end,
chunksTotal: response.chunksTotal,
chunksUploaded: response.chunksUploaded
});
@ -4329,8 +4330,8 @@
if (onProgress) {
onProgress({
$id: response.$id,
progress: Math.min((counter + 1) * Appwrite.CHUNK_SIZE, size) / size * 100,
sizeUploaded: end + 1,
progress: Math.min((counter + 1) * Appwrite.CHUNK_SIZE - 1, size) / size * 100,
sizeUploaded: end,
chunksTotal: response.chunksTotal,
chunksUploaded: response.chunksUploaded
});
@ -4744,6 +4745,34 @@
'content-type': 'application/json',
}, payload);
}),
/**
* List Team Logs
*
* Get the team activity logs list by its unique ID.
*
* @param {string} teamId
* @param {number} limit
* @param {number} offset
* @throws {AppwriteException}
* @returns {Promise}
*/
listLogs: (teamId, limit, offset) => __awaiter(this, void 0, void 0, function* () {
if (typeof teamId === 'undefined') {
throw new AppwriteException('Missing required parameter: "teamId"');
}
let path = '/teams/{teamId}/logs'.replace('{teamId}', teamId);
let payload = {};
if (typeof limit !== 'undefined') {
payload['limit'] = limit;
}
if (typeof offset !== 'undefined') {
payload['offset'] = offset;
}
const uri = new URL(this.config.endpoint + path);
return yield this.call('get', uri, {
'content-type': 'application/json',
}, payload);
}),
/**
* Get Team Memberships
*
@ -5536,7 +5565,7 @@
var _a, _b;
return __awaiter(this, void 0, void 0, function* () {
method = method.toUpperCase();
headers = Object.assign(Object.assign({}, headers), this.headers);
headers = Object.assign({}, this.headers, headers);
let options = {
method,
headers,

View file

@ -16,10 +16,19 @@
"keyID": "oauth2AppleKeyId",
"teamID": "oauth2AppleTeamId",
"p8": "oauth2AppleP8"
},
"Okta": {
"clientSecret": "oauth2OktaClientSecret",
"oktaDomain": "oauth2OktaDomain",
"authorizationServerId": "oauth2OktaAuthorizationServerId"
},
"Auth0": {
"clientSecret": "oauth2Auth0ClientSecret",
"auth0Domain": "oauth2Auth0Domain"
}
}
let provider = element.getAttribute("data-forms-oauth-custom");
if (!provider || !providers.hasOwnProperty(provider)) { console.error("Provider for custom form not set or unkown") }
if (!provider || !providers.hasOwnProperty(provider)) { console.error("Provider for custom form not set or unknown") }
let config = providers[provider];
// Add Change Listeners for element

View file

@ -34,7 +34,7 @@
alert: function(text, classname) {
return function(alerts) {
alerts.add({ text: text, class: classname || "success" }, 3000);
alerts.add({ text: text, class: classname || "success" }, 6000);
};
},

View file

@ -3,6 +3,7 @@
[data-views-current="1"] {
.scroll-to {
opacity: 0!important;
pointer-events: none;
}
}

View file

@ -96,6 +96,10 @@
}
}
[data-views-current]:not([data-views-current="0"]):not([data-views-current="1"]) .upload-box {
margin-right: 4rem;
}
.icon-button {
--p-icon-button-size:var(--icon-button-size, 20px);

View file

@ -1,10 +1,10 @@
@font-face {font-family: "icon";
src: url('/fonts/icon.eot?t=1645269945236'); /* IE9*/
src: url('/fonts/icon.eot?t=1645269945236#iefix') format('embedded-opentype'), /* IE6-IE8 */
url("/fonts/icon.woff2?t=1645269945236") format("woff2"),
url("/fonts/icon.woff?t=1645269945236") format("woff"),
url('/fonts/icon.ttf?t=1645269945236') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/
url('/fonts/icon.svg?t=1645269945236#icon') format('svg'); /* iOS 4.1- */
src: url('/fonts/icon.eot?t=1650537780691'); /* IE9*/
src: url('/fonts/icon.eot?t=1650537780691#iefix') format('embedded-opentype'), /* IE6-IE8 */
url("/fonts/icon.woff2?t=1650537780691") format("woff2"),
url("/fonts/icon.woff?t=1650537780691") format("woff"),
url('/fonts/icon.ttf?t=1650537780691') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/
url('/fonts/icon.svg?t=1650537780691#icon') format('svg'); /* iOS 4.1- */
font-weight: normal;
font-style: normal;
}
@ -17,7 +17,6 @@
vertical-align: middle;
display: inline-block;
text-decoration: inherit;
width: 1em;
margin-right: .2em;
text-align: center;
/* opacity: .8; */
@ -79,101 +78,101 @@
.icon-down-open:before { content: "\ea22"; }
.icon-download:before { content: "\ea23"; }
.icon-edit:before { content: "\ea24"; }
.icon-enum:before { content: "\ea25"; }
.icon-export:before { content: "\ea26"; }
.icon-eye:before { content: "\ea27"; }
.icon-facebook:before { content: "\ea28"; }
.icon-file:before { content: "\ea29"; }
.icon-film:before { content: "\ea2a"; }
.icon-filter:before { content: "\ea2b"; }
.icon-fire:before { content: "\ea2c"; }
.icon-float:before { content: "\ea2d"; }
.icon-folder:before { content: "\ea2e"; }
.icon-github:before { content: "\ea2f"; }
.icon-gitlab:before { content: "\ea30"; }
.icon-glasses:before { content: "\ea31"; }
.icon-google:before { content: "\ea32"; }
.icon-hackernews:before { content: "\ea33"; }
.icon-header:before { content: "\ea34"; }
.icon-heart:before { content: "\ea35"; }
.icon-home:before { content: "\ea36"; }
.icon-image:before { content: "\ea37"; }
.icon-inbox:before { content: "\ea38"; }
.icon-info-circled:before { content: "\ea39"; }
.icon-instagram:before { content: "\ea3a"; }
.icon-integer:before { content: "\ea3b"; }
.icon-ip:before { content: "\ea3c"; }
.icon-italic:before { content: "\ea3d"; }
.icon-key-inv:before { content: "\ea3e"; }
.icon-key:before { content: "\ea3f"; }
.icon-keyboard:before { content: "\ea40"; }
.icon-lamp:before { content: "\ea41"; }
.icon-left-dir:before { content: "\ea42"; }
.icon-left-open:before { content: "\ea43"; }
.icon-lifebuoy:before { content: "\ea44"; }
.icon-lightning:before { content: "\ea45"; }
.icon-link-ext:before { content: "\ea46"; }
.icon-link:before { content: "\ea47"; }
.icon-url:before { content: "\ea47"; }
.icon-linkedin:before { content: "\ea48"; }
.icon-list-bullet:before { content: "\ea49"; }
.icon-list-numbered:before { content: "\ea4a"; }
.icon-list:before { content: "\ea4b"; }
.icon-location:before { content: "\ea4c"; }
.icon-lock:before { content: "\ea4d"; }
.icon-login:before { content: "\ea4e"; }
.icon-mail:before { content: "\ea4f"; }
.icon-email:before { content: "\ea4f"; }
.icon-medium:before { content: "\ea50"; }
.icon-menu:before { content: "\ea51"; }
.icon-minus:before { content: "\ea52"; }
.icon-moon-inv:before { content: "\ea53"; }
.icon-moon:before { content: "\ea54"; }
.icon-more:before { content: "\ea55"; }
.icon-network:before { content: "\ea56"; }
.icon-ok-circled:before { content: "\ea57"; }
.icon-ok:before { content: "\ea58"; }
.icon-palette:before { content: "\ea59"; }
.icon-pause:before { content: "\ea5a"; }
.icon-phone:before { content: "\ea5b"; }
.icon-photograph:before { content: "\ea5c"; }
.icon-picture:before { content: "\ea5d"; }
.icon-pinterest:before { content: "\ea5e"; }
.icon-play:before { content: "\ea5f"; }
.icon-plus:before { content: "\ea60"; }
.icon-qrcode:before { content: "\ea61"; }
.icon-question:before { content: "\ea62"; }
.icon-quote-right:before { content: "\ea63"; }
.icon-random:before { content: "\ea64"; }
.icon-reddit:before { content: "\ea65"; }
.icon-refresh:before { content: "\ea66"; }
.icon-right-dir:before { content: "\ea67"; }
.icon-right-open:before { content: "\ea68"; }
.icon-rocket:before { content: "\ea69"; }
.icon-search:before { content: "\ea6a"; }
.icon-shield:before { content: "\ea6b"; }
.icon-shuffle:before { content: "\ea6c"; }
.icon-smile-o:before { content: "\ea6d"; }
.icon-sort:before { content: "\ea6e"; }
.icon-stackoverflow:before { content: "\ea6f"; }
.icon-stopwatch:before { content: "\ea70"; }
.icon-string:before { content: "\ea71"; }
.icon-sun-inv:before { content: "\ea72"; }
.icon-telegram:before { content: "\ea73"; }
.icon-trash-2:before { content: "\ea74"; }
.icon-trash:before { content: "\ea75"; }
.icon-twitter:before { content: "\ea76"; }
.icon-underline:before { content: "\ea77"; }
.icon-up-dir:before { content: "\ea78"; }
.icon-up-open:before { content: "\ea79"; }
.icon-upload:before { content: "\ea7a"; }
.icon-user:before { content: "\ea7b"; }
.icon-users:before { content: "\ea7c"; }
.icon-videocam:before { content: "\ea7d"; }
.icon-warning:before { content: "\ea7e"; }
.icon-whatsapp:before { content: "\ea7f"; }
.icon-wheelchair:before { content: "\ea80"; }
.icon-windows:before { content: "\ea81"; }
.icon-wrench:before { content: "\ea82"; }
.icon-x:before { content: "\ea83"; }
.icon-youtube-play:before { content: "\ea84"; }
.icon-email:before { content: "\ea25"; }
.icon-enum:before { content: "\ea26"; }
.icon-export:before { content: "\ea27"; }
.icon-eye:before { content: "\ea28"; }
.icon-facebook:before { content: "\ea29"; }
.icon-file:before { content: "\ea2a"; }
.icon-film:before { content: "\ea2b"; }
.icon-filter:before { content: "\ea2c"; }
.icon-fire:before { content: "\ea2d"; }
.icon-float:before { content: "\ea2e"; }
.icon-folder:before { content: "\ea2f"; }
.icon-github:before { content: "\ea30"; }
.icon-gitlab:before { content: "\ea31"; }
.icon-glasses:before { content: "\ea32"; }
.icon-google:before { content: "\ea33"; }
.icon-hackernews:before { content: "\ea34"; }
.icon-header:before { content: "\ea35"; }
.icon-heart:before { content: "\ea36"; }
.icon-home:before { content: "\ea37"; }
.icon-image:before { content: "\ea38"; }
.icon-inbox:before { content: "\ea39"; }
.icon-info-circled:before { content: "\ea3a"; }
.icon-instagram:before { content: "\ea3b"; }
.icon-integer:before { content: "\ea3c"; }
.icon-ip:before { content: "\ea3d"; }
.icon-italic:before { content: "\ea3e"; }
.icon-key-inv:before { content: "\ea3f"; }
.icon-key:before { content: "\ea40"; }
.icon-keyboard:before { content: "\ea41"; }
.icon-lamp:before { content: "\ea42"; }
.icon-left-dir:before { content: "\ea43"; }
.icon-left-open:before { content: "\ea44"; }
.icon-lifebuoy:before { content: "\ea45"; }
.icon-lightning:before { content: "\ea46"; }
.icon-link-ext:before { content: "\ea47"; }
.icon-link:before { content: "\ea48"; }
.icon-linkedin:before { content: "\ea49"; }
.icon-list-bullet:before { content: "\ea4a"; }
.icon-list-numbered:before { content: "\ea4b"; }
.icon-list:before { content: "\ea4c"; }
.icon-location:before { content: "\ea4d"; }
.icon-lock:before { content: "\ea4e"; }
.icon-login:before { content: "\ea4f"; }
.icon-mail:before { content: "\ea50"; }
.icon-medium:before { content: "\ea51"; }
.icon-menu:before { content: "\ea52"; }
.icon-minus:before { content: "\ea53"; }
.icon-moon-inv:before { content: "\ea54"; }
.icon-moon:before { content: "\ea55"; }
.icon-more:before { content: "\ea56"; }
.icon-network:before { content: "\ea57"; }
.icon-ok-circled:before { content: "\ea58"; }
.icon-ok:before { content: "\ea59"; }
.icon-palette:before { content: "\ea5a"; }
.icon-pause:before { content: "\ea5b"; }
.icon-phone:before { content: "\ea5c"; }
.icon-photograph:before { content: "\ea5d"; }
.icon-picture:before { content: "\ea5e"; }
.icon-pinterest:before { content: "\ea5f"; }
.icon-play:before { content: "\ea60"; }
.icon-plus:before { content: "\ea61"; }
.icon-qrcode:before { content: "\ea62"; }
.icon-question:before { content: "\ea63"; }
.icon-quote-right:before { content: "\ea64"; }
.icon-random:before { content: "\ea65"; }
.icon-reddit:before { content: "\ea66"; }
.icon-refresh:before { content: "\ea67"; }
.icon-right-dir:before { content: "\ea68"; }
.icon-right-open:before { content: "\ea69"; }
.icon-rocket:before { content: "\ea6a"; }
.icon-search:before { content: "\ea6b"; }
.icon-shield:before { content: "\ea6c"; }
.icon-shuffle:before { content: "\ea6d"; }
.icon-smile-o:before { content: "\ea6e"; }
.icon-sort:before { content: "\ea6f"; }
.icon-stackoverflow:before { content: "\ea70"; }
.icon-stopwatch:before { content: "\ea71"; }
.icon-string:before { content: "\ea72"; }
.icon-sun-inv:before { content: "\ea73"; }
.icon-telegram:before { content: "\ea74"; }
.icon-trash-2:before { content: "\ea75"; }
.icon-trash:before { content: "\ea76"; }
.icon-twitter:before { content: "\ea77"; }
.icon-underline:before { content: "\ea78"; }
.icon-up-dir:before { content: "\ea79"; }
.icon-up-open:before { content: "\ea7a"; }
.icon-upload:before { content: "\ea7b"; }
.icon-url:before { content: "\ea7c"; }
.icon-user:before { content: "\ea7d"; }
.icon-users:before { content: "\ea7e"; }
.icon-videocam:before { content: "\ea7f"; }
.icon-warning:before { content: "\ea80"; }
.icon-whatsapp:before { content: "\ea81"; }
.icon-wheelchair:before { content: "\ea82"; }
.icon-windows:before { content: "\ea83"; }
.icon-wrench:before { content: "\ea84"; }
.icon-x:before { content: "\ea85"; }
.icon-youtube-play:before { content: "\ea86"; }

View file

@ -0,0 +1,210 @@
<?php
namespace Appwrite\Auth\OAuth2;
use Appwrite\Auth\OAuth2;
// Reference Material
// https://auth0.com/docs/api/authentication
class Auth0 extends OAuth2
{
/**
* @var array
*/
protected $scopes = [
'openid',
'profile',
'email',
'offline_access'
];
/**
* @var array
*/
protected $user = [];
/**
* @var array
*/
protected $tokens = [];
/**
* @return string
*/
public function getName(): string
{
return 'auth0';
}
/**
* @return string
*/
public function getLoginURL(): string
{
return 'https://'.$this->getAuth0Domain().'/authorize?'.\http_build_query([
'client_id' => $this->appID,
'redirect_uri' => $this->callback,
'state'=> \json_encode($this->state),
'scope'=> \implode(' ', $this->getScopes()),
'response_type' => 'code'
]);
}
/**
* @param string $code
*
* @return array
*/
protected function getTokens(string $code): array
{
if(empty($this->tokens)) {
$headers = ['Content-Type: application/x-www-form-urlencoded'];
$this->tokens = \json_decode($this->request(
'POST',
'https://'.$this->getAuth0Domain().'/oauth/token',
$headers,
\http_build_query([
'code' => $code,
'client_id' => $this->appID,
'client_secret' => $this->getClientSecret(),
'redirect_uri' => $this->callback,
'scope' => \implode(' ', $this->getScopes()),
'grant_type' => 'authorization_code'
])
), true);
}
return $this->tokens;
}
/**
* @param string $refreshToken
*
* @return array
*/
public function refreshTokens(string $refreshToken): array
{
$headers = ['Content-Type: application/x-www-form-urlencoded'];
$this->tokens = \json_decode($this->request(
'POST',
'https://'.$this->getAuth0Domain().'/oauth/token',
$headers,
\http_build_query([
'refresh_token' => $refreshToken,
'client_id' => $this->appID,
'client_secret' => $this->getClientSecret(),
'grant_type' => 'refresh_token'
])
), true);
if(empty($this->tokens['refresh_token'])) {
$this->tokens['refresh_token'] = $refreshToken;
}
return $this->tokens;
}
/**
* @param string $accessToken
*
* @return string
*/
public function getUserID(string $accessToken): string
{
$user = $this->getUser($accessToken);
if (isset($user['sub'])) {
return $user['sub'];
}
return '';
}
/**
* @param string $accessToken
*
* @return string
*/
public function getUserEmail(string $accessToken): string
{
$user = $this->getUser($accessToken);
if (isset($user['email'])) {
return $user['email'];
}
return '';
}
/**
* @param string $accessToken
*
* @return string
*/
public function getUserName(string $accessToken): string
{
$user = $this->getUser($accessToken);
if (isset($user['name'])) {
return $user['name'];
}
return '';
}
/**
* @param string $accessToken
*
* @return array
*/
protected function getUser(string $accessToken): array
{
if (empty($this->user)) {
$headers = ['Authorization: Bearer '. \urlencode($accessToken)];
$user = $this->request('GET', 'https://'.$this->getAuth0Domain().'/userinfo', $headers);
$this->user = \json_decode($user, true);
}
return $this->user;
}
/**
* Extracts the Client Secret from the JSON stored in appSecret
*
* @return string
*/
protected function getClientSecret(): string
{
$secret = $this->getAppSecret();
return (isset($secret['clientSecret'])) ? $secret['clientSecret'] : '';
}
/**
* Extracts the Auth0 Domain from the JSON stored in appSecret
*
* @return string
*/
protected function getAuth0Domain(): string
{
$secret = $this->getAppSecret();
return (isset($secret['auth0Domain'])) ? $secret['auth0Domain'] : '';
}
/**
* Decode the JSON stored in appSecret
*
* @return array
*/
protected function getAppSecret(): array
{
try {
$secret = \json_decode($this->appSecret, true, 512, JSON_THROW_ON_ERROR);
} catch (\Throwable $th) {
throw new \Exception('Invalid secret');
}
return $secret;
}
}

View file

@ -0,0 +1,221 @@
<?php
namespace Appwrite\Auth\OAuth2;
use Appwrite\Auth\OAuth2;
// Reference Material
// https://developer.okta.com/docs/guides/sign-into-web-app-redirect/php/main/
class Okta extends OAuth2
{
/**
* @var array
*/
protected $scopes = [
'openid',
'profile',
'email',
'offline_access'
];
/**
* @var array
*/
protected $user = [];
/**
* @var array
*/
protected $tokens = [];
/**
* @return string
*/
public function getName(): string
{
return 'okta';
}
/**
* @return string
*/
public function getLoginURL(): string
{
return 'https://'.$this->getOktaDomain().'/oauth2/'.$this->getAuthorizationServerId().'/v1/authorize?'.\http_build_query([
'client_id' => $this->appID,
'redirect_uri' => $this->callback,
'state'=> \json_encode($this->state),
'scope'=> \implode(' ', $this->getScopes()),
'response_type' => 'code'
]);
}
/**
* @param string $code
*
* @return array
*/
protected function getTokens(string $code): array
{
if(empty($this->tokens)) {
$headers = ['Content-Type: application/x-www-form-urlencoded'];
$this->tokens = \json_decode($this->request(
'POST',
'https://'.$this->getOktaDomain().'/oauth2/'.$this->getAuthorizationServerId().'/v1/token',
$headers,
\http_build_query([
'code' => $code,
'client_id' => $this->appID,
'client_secret' => $this->getClientSecret(),
'redirect_uri' => $this->callback,
'scope' => \implode(' ', $this->getScopes()),
'grant_type' => 'authorization_code'
])
), true);
}
return $this->tokens;
}
/**
* @param string $refreshToken
*
* @return array
*/
public function refreshTokens(string $refreshToken): array
{
$headers = ['Content-Type: application/x-www-form-urlencoded'];
$this->tokens = \json_decode($this->request(
'POST',
'https://'.$this->getOktaDomain().'/oauth2/'.$this->getAuthorizationServerId().'/v1/token',
$headers,
\http_build_query([
'refresh_token' => $refreshToken,
'client_id' => $this->appID,
'client_secret' => $this->getClientSecret(),
'grant_type' => 'refresh_token'
])
), true);
if(empty($this->tokens['refresh_token'])) {
$this->tokens['refresh_token'] = $refreshToken;
}
return $this->tokens;
}
/**
* @param string $accessToken
*
* @return string
*/
public function getUserID(string $accessToken): string
{
$user = $this->getUser($accessToken);
if (isset($user['sub'])) {
return $user['sub'];
}
return '';
}
/**
* @param string $accessToken
*
* @return string
*/
public function getUserEmail(string $accessToken): string
{
$user = $this->getUser($accessToken);
if (isset($user['email'])) {
return $user['email'];
}
return '';
}
/**
* @param string $accessToken
*
* @return string
*/
public function getUserName(string $accessToken): string
{
$user = $this->getUser($accessToken);
if (isset($user['name'])) {
return $user['name'];
}
return '';
}
/**
* @param string $accessToken
*
* @return array
*/
protected function getUser(string $accessToken): array
{
if (empty($this->user)) {
$headers = ['Authorization: Bearer '. \urlencode($accessToken)];
$user = $this->request('GET', 'https://'.$this->getOktaDomain().'/oauth2/'.$this->getAuthorizationServerId().'/v1/userinfo', $headers);
$this->user = \json_decode($user, true);
}
return $this->user;
}
/**
* Extracts the Client Secret from the JSON stored in appSecret
*
* @return string
*/
protected function getClientSecret(): string
{
$secret = $this->getAppSecret();
return (isset($secret['clientSecret'])) ? $secret['clientSecret'] : '';
}
/**
* Extracts the Okta Domain from the JSON stored in appSecret
*
* @return string
*/
protected function getOktaDomain(): string
{
$secret = $this->getAppSecret();
return (isset($secret['oktaDomain'])) ? $secret['oktaDomain'] : '';
}
/**
* Extracts the Okta Authorization Server ID from the JSON stored in appSecret
*
* @return string
*/
protected function getAuthorizationServerId(): string
{
$secret = $this->getAppSecret();
return (isset($secret['authorizationServerId'])) ? $secret['authorizationServerId'] : 'default';
}
/**
* Decode the JSON stored in appSecret
*
* @return array
*/
protected function getAppSecret(): array
{
try {
$secret = \json_decode($this->appSecret, true, 512, JSON_THROW_ON_ERROR);
} catch (\Throwable $th) {
throw new \Exception('Invalid secret');
}
return $secret;
}
}

View file

@ -16,9 +16,6 @@ class Event
const AUDITS_QUEUE_NAME = 'v1-audits';
const AUDITS_CLASS_NAME = 'AuditsV1';
const USAGE_QUEUE_NAME = 'v1-usage';
const USAGE_CLASS_NAME = 'UsageV1';
const MAILS_QUEUE_NAME = 'v1-mails';
const MAILS_CLASS_NAME = 'MailsV1';

View file

@ -145,6 +145,7 @@ class Exception extends \Exception
const PROJECT_INVALID_SUCCESS_URL = 'project_invalid_success_url';
const PROJECT_INVALID_FAILURE_URL = 'project_invalid_failure_url';
const PROJECT_MISSING_USER_ID = 'project_missing_user_id';
const PROJECT_RESERVED_PROJECT = 'project_reserved_project';
/** Webhooks */
const WEBHOOK_NOT_FOUND = 'webhook_not_found';

View file

@ -0,0 +1,25 @@
<?php
namespace Appwrite\Utopia\Database\Validator;
use Utopia\Database\Document;
use Utopia\Database\Validator\OrderAttributes as ValidatorOrderAttributes;
class OrderAttributes extends ValidatorOrderAttributes
{
/**
* Expression constructor
*
* @param Document[] $attributes
* @param Document[] $indexes
* @param bool $strict
*/
public function __construct($attributes, $indexes, $strict)
{
// Remove failed/stuck/processing indexes
$indexes = \array_filter($indexes, function($index) {
return $index->getAttribute('status') === 'available';
});
parent::__construct($attributes, $indexes, $strict);
}
}

View file

@ -0,0 +1,25 @@
<?php
namespace Appwrite\Utopia\Database\Validator;
use Utopia\Database\Document;
use Utopia\Database\Validator\Queries as ValidatorQueries;
class Queries extends ValidatorQueries
{
/**
* Expression constructor
*
* @param Document[] $attributes
* @param Document[] $indexes
* @param bool $strict
*/
public function __construct($attributes, $indexes, $strict)
{
// Remove failed/stuck/processing indexes
$indexes = \array_filter($indexes, function($index) {
return $index->getAttribute('status') === 'available';
});
parent::__construct($attributes, $indexes, $strict);
}
}

View file

@ -53,9 +53,9 @@ class Execution extends Model
'default' => 0,
'example' => 0,
])
->addRule('stdout', [
->addRule('response', [
'type' => self::TYPE_STRING,
'description' => 'The script stdout output string. Logs the last 4,000 characters of the execution stdout output.',
'description' => 'The script response output string. Logs the last 4,000 characters of the execution response output.',
'default' => '',
'example' => '',
])

View file

@ -445,7 +445,7 @@ trait AccountBase
{
$email = $data['email'] ?? '';
$session = $data['session'] ?? '';
$newName = 'New Name';
$newName = 'Lorem';
/**
* Test for SUCCESS
@ -477,7 +477,7 @@ trait AccountBase
]));
$this->assertEquals($response['headers']['status-code'], 401);
$response = $this->client->call(Client::METHOD_PATCH, '/account/name', array_merge([
'origin' => 'http://localhost',
'content-type' => 'application/json',
@ -485,7 +485,7 @@ trait AccountBase
'cookie' => 'a_session_'.$this->getProject()['$id'].'=' . $session,
]), [
]);
$this->assertEquals($response['headers']['status-code'], 400);
$response = $this->client->call(Client::METHOD_PATCH, '/account/name', array_merge([
@ -496,7 +496,7 @@ trait AccountBase
]), [
'name' => 'ocSRq1d3QphHivJyUmYY7WMnrxyjdk5YvVwcDqx2zS0coxESN8RmsQwLWw5Whnf0WbVohuFWTRAaoKgCOO0Y0M7LwgFnZmi8881Y72222222222222222222222222222'
]);
$this->assertEquals($response['headers']['status-code'], 400);
$data['name'] = $newName;
@ -532,7 +532,6 @@ trait AccountBase
$this->assertNotEmpty($response['body']['$id']);
$this->assertIsNumeric($response['body']['registration']);
$this->assertEquals($response['body']['email'], $email);
$this->assertEquals($response['body']['name'], 'New Name');
$response = $this->client->call(Client::METHOD_POST, '/account/sessions', array_merge([
'origin' => 'http://localhost',
@ -625,7 +624,6 @@ trait AccountBase
$this->assertNotEmpty($response['body']['$id']);
$this->assertIsNumeric($response['body']['registration']);
$this->assertEquals($response['body']['email'], $newEmail);
$this->assertEquals($response['body']['name'], 'New Name');
/**
* Test for FAILURE
@ -637,7 +635,7 @@ trait AccountBase
]));
$this->assertEquals($response['headers']['status-code'], 401);
$response = $this->client->call(Client::METHOD_PATCH, '/account/email', array_merge([
'origin' => 'http://localhost',
'content-type' => 'application/json',
@ -645,7 +643,7 @@ trait AccountBase
'cookie' => 'a_session_'.$this->getProject()['$id'].'=' . $session,
]), [
]);
$this->assertEquals($response['headers']['status-code'], 400);
// Test if we can create a new account with the old email
@ -1279,7 +1277,7 @@ trait AccountBase
$expireTime = strpos($lastEmail['text'], 'expire='.$response['body']['expire'], 0);
$this->assertNotFalse($expireTime);
$secretTest = strpos($lastEmail['text'], 'secret='.$response['body']['secret'], 0);
$this->assertNotFalse($secretTest);
@ -1339,6 +1337,7 @@ trait AccountBase
{
$id = $data['id'] ?? '';
$token = $data['token'] ?? '';
$email = $data['email'] ?? '';
/**
* Test for SUCCESS
@ -1361,6 +1360,20 @@ trait AccountBase
$sessionId = $response['body']['$id'];
$session = $this->client->parseCookie((string)$response['headers']['set-cookie'])['a_session_'.$this->getProject()['$id']];
$response = $this->client->call(Client::METHOD_GET, '/account', array_merge([
'origin' => 'http://localhost',
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'cookie' => 'a_session_'.$this->getProject()['$id'].'=' . $session,
]));
$this->assertEquals($response['headers']['status-code'], 200);
$this->assertNotEmpty($response['body']);
$this->assertNotEmpty($response['body']['$id']);
$this->assertIsNumeric($response['body']['registration']);
$this->assertEquals($response['body']['email'], $email);
$this->assertTrue($response['body']['emailVerification']);
/**
* Test for FAILURE
*/

View file

@ -510,4 +510,85 @@ class AccountCustomClientTest extends Scope
$this->assertEquals($response['headers']['status-code'], 404);
}
}
/**
* @depends testUpdateAccountName
*/
public function testUpdateAccountNameSearch($data): void
{
$id = $data['id'] ?? '';
$email = $data['email'] ?? '';
$newName = 'Lorem';
/**
* Test for SUCCESS
*/
$response = $this->client->call(Client::METHOD_GET, '/users', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
], [
'search' => $newName
]);
$this->assertEquals($response['headers']['status-code'], 200);
$this->assertNotEmpty($response['body']);
$this->assertNotEmpty($response['body']['users']);
$this->assertCount(1, $response['body']['users']);
$this->assertEquals($response['body']['users'][0]['email'], $email);
$response = $this->client->call(Client::METHOD_GET, '/users', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
], [
'search' => $id
]);
$this->assertEquals($response['headers']['status-code'], 200);
$this->assertNotEmpty($response['body']);
$this->assertNotEmpty($response['body']['users']);
$this->assertCount(1, $response['body']['users']);
$this->assertEquals($response['body']['users'][0]['email'], $email);
}
/**
* @depends testUpdateAccountEmail
*/
public function testUpdateAccountEmailSearch($data): void
{
$id = $data['id'] ?? '';
$email = $data['email'] ?? '';
/**
* Test for SUCCESS
*/
$response = $this->client->call(Client::METHOD_GET, '/users', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
], [
'search' => '"' . $email . '"'
]);
$this->assertEquals($response['headers']['status-code'], 200);
$this->assertNotEmpty($response['body']);
$this->assertNotEmpty($response['body']['users']);
$this->assertCount(1, $response['body']['users']);
$this->assertEquals($response['body']['users'][0]['email'], $email);
$response = $this->client->call(Client::METHOD_GET, '/users', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
], [
'search' => $id
]);
$this->assertEquals($response['headers']['status-code'], 200);
$this->assertNotEmpty($response['body']);
$this->assertNotEmpty($response['body']['users']);
$this->assertCount(1, $response['body']['users']);
$this->assertEquals($response['body']['users'][0]['email'], $email);
}
}

View file

@ -33,7 +33,7 @@ class AccountCustomServerTest extends Scope
]);
$this->assertEquals(401, $response['headers']['status-code']);
return [];
}
}

View file

@ -1428,6 +1428,7 @@ trait DatabaseBase
]), [
'key' => 'probability',
'required' => false,
'default' => 0,
'min' => 0,
'max' => 1,
]);

View file

@ -208,7 +208,7 @@ class FunctionsCustomClientTest extends Scope
'x-appwrite-key' => $apikey,
]);
$output = json_decode($executions['body']['stdout'], true);
$output = json_decode($executions['body']['response'], true);
$this->assertEquals(200, $executions['headers']['status-code']);
$this->assertEquals('completed', $executions['body']['status']);
$this->assertEquals($functionId, $output['APPWRITE_FUNCTION_ID']);
@ -383,7 +383,7 @@ class FunctionsCustomClientTest extends Scope
'async' => false
]);
$output = json_decode($execution['body']['stdout'], true);
$output = json_decode($execution['body']['response'], true);
$this->assertEquals(201, $execution['headers']['status-code']);
$this->assertEquals('completed', $execution['body']['status']);
$this->assertEquals($functionId, $output['APPWRITE_FUNCTION_ID']);

View file

@ -490,7 +490,7 @@ class FunctionsCustomServerTest extends Scope
$this->assertEquals($data['functionId'], $execution['body']['functionId']);
$this->assertEquals('waiting', $execution['body']['status']);
$this->assertEquals(0, $execution['body']['statusCode']);
$this->assertEquals('', $execution['body']['stdout']);
$this->assertEquals('', $execution['body']['response']);
$this->assertEquals('', $execution['body']['stderr']);
$this->assertEquals(0, $execution['body']['time']);
@ -507,13 +507,13 @@ class FunctionsCustomServerTest extends Scope
$this->assertEquals($data['functionId'], $execution['body']['functionId']);
$this->assertEquals('completed', $execution['body']['status']);
$this->assertEquals(200, $execution['body']['statusCode']);
$this->assertStringContainsString($execution['body']['functionId'], $execution['body']['stdout']);
$this->assertStringContainsString($data['deploymentId'], $execution['body']['stdout']);
$this->assertStringContainsString('Test1', $execution['body']['stdout']);
$this->assertStringContainsString('http', $execution['body']['stdout']);
$this->assertStringContainsString('PHP', $execution['body']['stdout']);
$this->assertStringContainsString('8.0', $execution['body']['stdout']);
// $this->assertStringContainsString('êä', $execution['body']['stdout']); // tests unknown utf-8 chars
$this->assertStringContainsString($execution['body']['functionId'], $execution['body']['response']);
$this->assertStringContainsString($data['deploymentId'], $execution['body']['response']);
$this->assertStringContainsString('Test1', $execution['body']['response']);
$this->assertStringContainsString('http', $execution['body']['response']);
$this->assertStringContainsString('PHP', $execution['body']['response']);
$this->assertStringContainsString('8.0', $execution['body']['response']);
// $this->assertStringContainsString('êä', $execution['body']['response']); // tests unknown utf-8 chars
$this->assertEquals('', $execution['body']['stderr']);
$this->assertLessThan(0.500, $execution['body']['time']);
@ -596,11 +596,11 @@ class FunctionsCustomServerTest extends Scope
$this->assertEquals(201, $execution['headers']['status-code']);
$this->assertEquals('completed', $execution['body']['status']);
$this->assertStringContainsString($data['deploymentId'], $execution['body']['stdout']);
$this->assertStringContainsString('Test1', $execution['body']['stdout']);
$this->assertStringContainsString('http', $execution['body']['stdout']);
$this->assertStringContainsString('PHP', $execution['body']['stdout']);
$this->assertStringContainsString('8.0', $execution['body']['stdout']);
$this->assertStringContainsString($data['deploymentId'], $execution['body']['response']);
$this->assertStringContainsString('Test1', $execution['body']['response']);
$this->assertStringContainsString('http', $execution['body']['response']);
$this->assertStringContainsString('PHP', $execution['body']['response']);
$this->assertStringContainsString('8.0', $execution['body']['response']);
// $this->assertStringContainsString('êä', $execution['body']['sdtout']); // tests unknown utf-8 chars
$this->assertLessThan(0.500, $execution['body']['time']);
@ -764,7 +764,7 @@ class FunctionsCustomServerTest extends Scope
$this->assertEquals($executions['body']['executions'][0]['statusCode'], 124);
$this->assertGreaterThan(2, $executions['body']['executions'][0]['time']);
$this->assertLessThan(3, $executions['body']['executions'][0]['time']);
$this->assertEquals($executions['body']['executions'][0]['stdout'], '');
$this->assertEquals($executions['body']['executions'][0]['response'], '');
$this->assertEquals($executions['body']['executions'][0]['stderr'], 'Execution timed out.');
// Cleanup : Delete function
@ -847,7 +847,7 @@ class FunctionsCustomServerTest extends Scope
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$output = json_decode($executions['body']['stdout'], true);
$output = json_decode($executions['body']['response'], true);
$this->assertEquals(200, $executions['headers']['status-code']);
$this->assertEquals('completed', $executions['body']['status']);
@ -875,7 +875,7 @@ class FunctionsCustomServerTest extends Scope
$this->assertCount(1, $executions['body']['executions']);
$this->assertEquals($executions['body']['executions'][0]['$id'], $executionId);
$this->assertEquals($executions['body']['executions'][0]['trigger'], 'http');
$this->assertStringContainsString('foobar', $executions['body']['executions'][0]['stdout']);
$this->assertStringContainsString('foobar', $executions['body']['executions'][0]['response']);
// Cleanup : Delete function
$response = $this->client->call(Client::METHOD_DELETE, '/functions/'. $functionId, [
@ -952,7 +952,7 @@ class FunctionsCustomServerTest extends Scope
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$output = json_decode($executions['body']['stdout'], true);
$output = json_decode($executions['body']['response'], true);
$this->assertEquals(200, $executions['headers']['status-code']);
$this->assertEquals('completed', $executions['body']['status']);
@ -981,7 +981,7 @@ class FunctionsCustomServerTest extends Scope
$this->assertCount(1, $executions['body']['executions']);
$this->assertEquals($executions['body']['executions'][0]['$id'], $executionId);
$this->assertEquals($executions['body']['executions'][0]['trigger'], 'http');
$this->assertStringContainsString('foobar', $executions['body']['executions'][0]['stdout']);
$this->assertStringContainsString('foobar', $executions['body']['executions'][0]['response']);
// Cleanup : Delete function
$response = $this->client->call(Client::METHOD_DELETE, '/functions/'. $functionId, [
@ -1057,7 +1057,7 @@ class FunctionsCustomServerTest extends Scope
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$output = json_decode($executions['body']['stdout'], true);
$output = json_decode($executions['body']['response'], true);
$this->assertEquals(200, $executions['headers']['status-code']);
$this->assertEquals('completed', $executions['body']['status']);
@ -1086,7 +1086,7 @@ class FunctionsCustomServerTest extends Scope
$this->assertCount(1, $executions['body']['executions']);
$this->assertEquals($executions['body']['executions'][0]['$id'], $executionId);
$this->assertEquals($executions['body']['executions'][0]['trigger'], 'http');
$this->assertStringContainsString('foobar', $executions['body']['executions'][0]['stdout']);
$this->assertStringContainsString('foobar', $executions['body']['executions'][0]['response']);
// Cleanup : Delete function
$response = $this->client->call(Client::METHOD_DELETE, '/functions/'. $functionId, [
@ -1162,7 +1162,7 @@ class FunctionsCustomServerTest extends Scope
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$output = json_decode($executions['body']['stdout'], true);
$output = json_decode($executions['body']['response'], true);
$this->assertEquals(200, $executions['headers']['status-code']);
$this->assertEquals('completed', $executions['body']['status']);
@ -1191,7 +1191,7 @@ class FunctionsCustomServerTest extends Scope
$this->assertCount(1, $executions['body']['executions']);
$this->assertEquals($executions['body']['executions'][0]['$id'], $executionId);
$this->assertEquals($executions['body']['executions'][0]['trigger'], 'http');
$this->assertStringContainsString('foobar', $executions['body']['executions'][0]['stdout']);
$this->assertStringContainsString('foobar', $executions['body']['executions'][0]['response']);
// Cleanup : Delete function
$response = $this->client->call(Client::METHOD_DELETE, '/functions/'. $functionId, [
@ -1267,7 +1267,7 @@ class FunctionsCustomServerTest extends Scope
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$output = json_decode($executions['body']['stdout'], true);
$output = json_decode($executions['body']['response'], true);
$this->assertEquals(200, $executions['headers']['status-code']);
$this->assertEquals('completed', $executions['body']['status']);
@ -1296,7 +1296,7 @@ class FunctionsCustomServerTest extends Scope
$this->assertCount(1, $executions['body']['executions']);
$this->assertEquals($executions['body']['executions'][0]['$id'], $executionId);
$this->assertEquals($executions['body']['executions'][0]['trigger'], 'http');
$this->assertStringContainsString('foobar', $executions['body']['executions'][0]['stdout']);
$this->assertStringContainsString('foobar', $executions['body']['executions'][0]['response']);
// Cleanup : Delete function
$response = $this->client->call(Client::METHOD_DELETE, '/functions/'. $functionId, [
@ -1372,7 +1372,7 @@ class FunctionsCustomServerTest extends Scope
// 'x-appwrite-project' => $this->getProject()['$id'],
// ], $this->getHeaders()));
// $output = json_decode($executions['body']['stdout'], true);
// $output = json_decode($executions['body']['response'], true);
// $this->assertEquals(200, $executions['headers']['status-code']);
// $this->assertEquals('completed', $executions['body']['status']);
@ -1401,7 +1401,7 @@ class FunctionsCustomServerTest extends Scope
// $this->assertCount(1, $executions['body']['executions']);
// $this->assertEquals($executions['body']['executions'][0]['$id'], $executionId);
// $this->assertEquals($executions['body']['executions'][0]['trigger'], 'http');
// $this->assertStringContainsString('foobar', $executions['body']['executions'][0]['stdout']);
// $this->assertStringContainsString('foobar', $executions['body']['executions'][0]['response']);
// // Cleanup : Delete function
// $response = $this->client->call(Client::METHOD_DELETE, '/functions/'. $functionId, [

View file

@ -146,27 +146,6 @@ class HealthCustomServerTest extends Scope
return [];
}
public function testUsageSuccess(): array
{
/**
* Test for SUCCESS
*/
$response = $this->client->call(Client::METHOD_GET, '/health/queue/usage', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), []);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertIsInt($response['body']['size']);
$this->assertLessThan(200, $response['body']['size']);
/**
* Test for FAILURE
*/
return [];
}
public function testCertificatesSuccess(): array
{
/**

View file

@ -281,6 +281,44 @@ trait UsersBase
return $data;
}
/**
* @depends testUpdateUserName
*/
public function testUpdateUserNameSearch($data): void
{
$id = $data['userId'] ?? '';
$newName = 'Updated name';
/**
* Test for SUCCESS
*/
$response = $this->client->call(Client::METHOD_GET, '/users', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'search' => $newName
]);
$this->assertEquals($response['headers']['status-code'], 200);
$this->assertNotEmpty($response['body']);
$this->assertNotEmpty($response['body']['users']);
$this->assertCount(1, $response['body']['users']);
$this->assertEquals($response['body']['users'][0]['$id'], $id);
$response = $this->client->call(Client::METHOD_GET, '/users', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'search' => $id
]);
$this->assertEquals($response['headers']['status-code'], 200);
$this->assertNotEmpty($response['body']);
$this->assertNotEmpty($response['body']['users']);
$this->assertCount(1, $response['body']['users']);
$this->assertEquals($response['body']['users'][0]['$id'], $id);
}
/**
* @depends testGetUser
*/
@ -310,6 +348,44 @@ trait UsersBase
return $data;
}
/**
* @depends testUpdateUserEmail
*/
public function testUpdateUserEmailSearch($data): void
{
$id = $data['userId'] ?? '';
$newEmail = '"users.service@updated.com"';
/**
* Test for SUCCESS
*/
$response = $this->client->call(Client::METHOD_GET, '/users', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'search' => $newEmail
]);
$this->assertEquals($response['headers']['status-code'], 200);
$this->assertNotEmpty($response['body']);
$this->assertNotEmpty($response['body']['users']);
$this->assertCount(1, $response['body']['users']);
$this->assertEquals($response['body']['users'][0]['$id'], $id);
$response = $this->client->call(Client::METHOD_GET, '/users', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'search' => $id
]);
$this->assertEquals($response['headers']['status-code'], 200);
$this->assertNotEmpty($response['body']);
$this->assertNotEmpty($response['body']['users']);
$this->assertCount(1, $response['body']['users']);
$this->assertEquals($response['body']['users'][0]['$id'], $id);
}
/**
* @depends testUpdateUserEmail
*/