mirror of
https://github.com/appwrite/appwrite
synced 2026-05-23 08:58:35 +00:00
Merge branch '0.16.x' of github.com:appwrite/appwrite into refactor-cache-date
This commit is contained in:
commit
b96b2ea2fb
165 changed files with 8414 additions and 4393 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -9,4 +9,5 @@
|
|||
.php_cs.cache
|
||||
debug/
|
||||
app/sdks
|
||||
dev/yasd_init.php
|
||||
dev/yasd_init.php
|
||||
.phpunit.result.cache
|
||||
|
|
|
|||
|
|
@ -142,7 +142,7 @@ Learn more at our [Technology Stack](#technology-stack) section.
|
|||
|
||||
##### Security
|
||||
|
||||
- [Appwrite Auth and ACL](https://github.com/appwrite/appwrite/blob/0.7.x/docs/specs/authentication.drawio.svg)
|
||||
- [Appwrite Auth and ACL](https://github.com/appwrite/appwrite/blob/master/docs/specs/authentication.drawio.svg)
|
||||
- [OAuth](https://en.wikipedia.org/wiki/OAuth)
|
||||
- [Encryption](https://medium.com/searchencrypt/what-is-encryption-how-does-it-work-e8f20e340537#:~:text=Encryption%20is%20a%20process%20that,%2C%20or%20decrypt%2C%20the%20information.)
|
||||
- [Hashing](https://searchsqlserver.techtarget.com/definition/hashing#:~:text=Hashing%20is%20the%20transformation%20of,it%20using%20the%20original%20value.)
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -121,6 +121,16 @@ return [ // Ordered by ABC.
|
|||
'beta' => false,
|
||||
'mock' => false,
|
||||
],
|
||||
'etsy' => [
|
||||
'name' => 'Etsy',
|
||||
'developers' => 'https://developers.etsy.com/',
|
||||
'icon' => 'icon-etsy',
|
||||
'enabled' => true,
|
||||
'sandbox' => false,
|
||||
'form' => false,
|
||||
'beta' => false,
|
||||
'mock' => false,
|
||||
],
|
||||
'facebook' => [
|
||||
'name' => 'Facebook',
|
||||
'developers' => 'https://developers.facebook.com/',
|
||||
|
|
|
|||
|
|
@ -52,20 +52,23 @@ $admins = [
|
|||
];
|
||||
|
||||
return [
|
||||
Auth::USER_ROLE_GUEST => [
|
||||
'label' => 'Guest',
|
||||
Auth::USER_ROLE_GUESTS => [
|
||||
'label' => 'Guests',
|
||||
'scopes' => [
|
||||
'public',
|
||||
'home',
|
||||
'console',
|
||||
'documents.read',
|
||||
'documents.write',
|
||||
'files.read',
|
||||
'files.write',
|
||||
'locale.read',
|
||||
'avatars.read',
|
||||
'execution.write',
|
||||
],
|
||||
],
|
||||
Auth::USER_ROLE_MEMBER => [
|
||||
'label' => 'Member',
|
||||
Auth::USER_ROLE_USERS => [
|
||||
'label' => 'Users',
|
||||
'scopes' => \array_merge($member, []),
|
||||
],
|
||||
Auth::USER_ROLE_ADMIN => [
|
||||
|
|
@ -80,8 +83,8 @@ return [
|
|||
'label' => 'Owner',
|
||||
'scopes' => \array_merge($member, $admins, []),
|
||||
],
|
||||
Auth::USER_ROLE_APP => [
|
||||
'label' => 'Application',
|
||||
Auth::USER_ROLE_APPS => [
|
||||
'label' => 'Applications',
|
||||
'scopes' => ['health.read'],
|
||||
],
|
||||
];
|
||||
|
|
|
|||
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
|
|
@ -6,30 +6,35 @@ use Appwrite\SMS\Adapter\Mock;
|
|||
use Appwrite\Auth\Validator\Password;
|
||||
use Appwrite\Auth\Validator\Phone;
|
||||
use Appwrite\Detector\Detector;
|
||||
use Appwrite\Event\Audit;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Mail;
|
||||
use Appwrite\Event\Phone as EventPhone;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Network\Validator\Email;
|
||||
use Appwrite\Network\Validator\Host;
|
||||
use Appwrite\Network\Validator\URL;
|
||||
use Appwrite\OpenSSL\OpenSSL;
|
||||
use Appwrite\Template\Template;
|
||||
use Appwrite\URL\URL as URLParser;
|
||||
use Appwrite\Utopia\Database\Validator\CustomId;
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Database\Validator\CustomId;
|
||||
use MaxMind\Db\Reader;
|
||||
use Utopia\App;
|
||||
use Appwrite\Event\Phone as EventPhone;
|
||||
use Utopia\Audit\Audit as EventAudit;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Exception\Duplicate;
|
||||
use Utopia\Database\ID;
|
||||
use Utopia\Database\Permission;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Role;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Locale\Locale;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\Assoc;
|
||||
use Utopia\Validator\Range;
|
||||
|
|
@ -92,19 +97,22 @@ App::post('/v1/account')
|
|||
}
|
||||
|
||||
try {
|
||||
$userId = $userId == 'unique()' ? $dbForProject->getId() : $userId;
|
||||
$userId = $userId == 'unique()' ? ID::unique() : $userId;
|
||||
$user = Authorization::skip(fn() => $dbForProject->createDocument('users', new Document([
|
||||
'$id' => $userId,
|
||||
'$read' => ['role:all'],
|
||||
'$write' => ['user:' . $userId],
|
||||
'$permissions' => [
|
||||
Permission::read(Role::any()),
|
||||
Permission::update(Role::user($userId)),
|
||||
Permission::delete(Role::user($userId)),
|
||||
],
|
||||
'email' => $email,
|
||||
'emailVerification' => false,
|
||||
'status' => true,
|
||||
'password' => Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS),
|
||||
'hash' => Auth::DEFAULT_ALGO,
|
||||
'hashOptions' => Auth::DEFAULT_ALGO_OPTIONS,
|
||||
'passwordUpdate' => \time(),
|
||||
'registration' => \time(),
|
||||
'passwordUpdate' => DateTime::now(),
|
||||
'registration' => DateTime::now(),
|
||||
'reset' => false,
|
||||
'name' => $name,
|
||||
'prefs' => new \stdClass(),
|
||||
|
|
@ -117,9 +125,9 @@ App::post('/v1/account')
|
|||
throw new Exception(Exception::USER_ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
Authorization::unsetRole('role:' . Auth::USER_ROLE_GUEST);
|
||||
Authorization::setRole('user:' . $user->getId());
|
||||
Authorization::setRole('role:' . Auth::USER_ROLE_MEMBER);
|
||||
Authorization::unsetRole(Role::guests()->toString());
|
||||
Authorization::setRole(Role::user($user->getId())->toString());
|
||||
Authorization::setRole(Role::users()->toString());
|
||||
|
||||
$events->setParam('userId', $user->getId());
|
||||
|
||||
|
|
@ -161,7 +169,8 @@ App::post('/v1/account/sessions/email')
|
|||
$protocol = $request->getProtocol();
|
||||
|
||||
$profile = $dbForProject->findOne('users', [
|
||||
new Query('email', Query::TYPE_EQUAL, [$email])]);
|
||||
Query::equal('email', [$email]),
|
||||
]);
|
||||
|
||||
if (!$profile || !Auth::passwordVerify($password, $profile->getAttribute('password'), $profile->getAttribute('hash'), $profile->getAttribute('hashOptions'))) {
|
||||
throw new Exception(Exception::USER_INVALID_CREDENTIALS);
|
||||
|
|
@ -173,17 +182,17 @@ App::post('/v1/account/sessions/email')
|
|||
|
||||
$detector = new Detector($request->getUserAgent('UNKNOWN'));
|
||||
$record = $geodb->get($request->getIP());
|
||||
$expiry = \time() + Auth::TOKEN_EXPIRATION_LOGIN_LONG;
|
||||
$expire = DateTime::addSeconds(new \DateTime(), Auth::TOKEN_EXPIRATION_LOGIN_LONG);
|
||||
$secret = Auth::tokenGenerator();
|
||||
$session = new Document(array_merge(
|
||||
[
|
||||
'$id' => $dbForProject->getId(),
|
||||
'$id' => ID::unique(),
|
||||
'userId' => $profile->getId(),
|
||||
'userInternalId' => $profile->getInternalId(),
|
||||
'provider' => Auth::SESSION_PROVIDER_EMAIL,
|
||||
'providerUid' => $email,
|
||||
'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak
|
||||
'expire' => $expiry,
|
||||
'expire' => $expire,
|
||||
'userAgent' => $request->getUserAgent('UNKNOWN'),
|
||||
'ip' => $request->getIP(),
|
||||
'countryCode' => ($record) ? \strtolower($record['country']['iso_code']) : '--',
|
||||
|
|
@ -193,7 +202,7 @@ App::post('/v1/account/sessions/email')
|
|||
$detector->getDevice()
|
||||
));
|
||||
|
||||
Authorization::setRole('user:' . $profile->getId());
|
||||
Authorization::setRole(Role::user($profile->getId())->toString());
|
||||
|
||||
// Re-hash if not using recommended algo
|
||||
if ($profile->getAttribute('hash') !== Auth::DEFAULT_ALGO) {
|
||||
|
|
@ -204,12 +213,15 @@ App::post('/v1/account/sessions/email')
|
|||
$dbForProject->updateDocument('users', $profile->getId(), $profile);
|
||||
}
|
||||
|
||||
$session = $dbForProject->createDocument('sessions', $session
|
||||
->setAttribute('$read', ['user:' . $profile->getId()])
|
||||
->setAttribute('$write', ['user:' . $profile->getId()]));
|
||||
|
||||
$dbForProject->deleteCachedDocument('users', $profile->getId());
|
||||
|
||||
$session = $dbForProject->createDocument('sessions', $session->setAttribute('$permissions', [
|
||||
Permission::read(Role::user($profile->getId())),
|
||||
Permission::update(Role::user($profile->getId())),
|
||||
Permission::delete(Role::user($profile->getId())),
|
||||
]));
|
||||
|
||||
|
||||
if (!Config::getParam('domainVerification')) {
|
||||
$response
|
||||
->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($profile->getId(), $secret)]))
|
||||
|
|
@ -217,8 +229,8 @@ App::post('/v1/account/sessions/email')
|
|||
}
|
||||
|
||||
$response
|
||||
->addCookie(Auth::$cookieName . '_legacy', Auth::encodeSession($profile->getId(), $secret), $expiry, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null)
|
||||
->addCookie(Auth::$cookieName, Auth::encodeSession($profile->getId(), $secret), $expiry, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite'))
|
||||
->addCookie(Auth::$cookieName . '_legacy', Auth::encodeSession($profile->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null)
|
||||
->addCookie(Auth::$cookieName, Auth::encodeSession($profile->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite'))
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
;
|
||||
|
||||
|
|
@ -443,8 +455,8 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
|
|||
}
|
||||
|
||||
$user = ($user->isEmpty()) ? $dbForProject->findOne('sessions', [ // Get user by provider id
|
||||
new Query('provider', QUERY::TYPE_EQUAL, [$provider]),
|
||||
new Query('providerUid', QUERY::TYPE_EQUAL, [$oauth2ID]),
|
||||
Query::equal('provider', [$provider]),
|
||||
Query::equal('providerUid', [$oauth2ID]),
|
||||
]) : $user;
|
||||
|
||||
if ($user === false || $user->isEmpty()) { // No user logged in or with OAuth2 provider ID, create new one or connect with account with same email
|
||||
|
|
@ -457,7 +469,8 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
|
|||
$isVerified = $oauth2->isEmailVerified($accessToken);
|
||||
|
||||
$user = $dbForProject->findOne('users', [
|
||||
new Query('email', Query::TYPE_EQUAL, [$email])]);
|
||||
Query::equal('email', [$email]),
|
||||
]);
|
||||
|
||||
if ($user === false || $user->isEmpty()) { // Last option -> create the user, generate random password
|
||||
$limit = $project->getAttribute('auths', [])['limit'] ?? 0;
|
||||
|
|
@ -471,19 +484,22 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
|
|||
}
|
||||
|
||||
try {
|
||||
$userId = $dbForProject->getId();
|
||||
$userId = ID::unique();
|
||||
$user = Authorization::skip(fn() => $dbForProject->createDocument('users', new Document([
|
||||
'$id' => $userId,
|
||||
'$read' => ['role:all'],
|
||||
'$write' => ['user:' . $userId],
|
||||
'$permissions' => [
|
||||
Permission::read(Role::any()),
|
||||
Permission::update(Role::user($userId)),
|
||||
Permission::delete(Role::user($userId)),
|
||||
],
|
||||
'email' => $email,
|
||||
'emailVerification' => true,
|
||||
'status' => true, // Email should already be authenticated by OAuth2 provider
|
||||
'password' => Auth::passwordHash(Auth::passwordGenerator(), Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS),
|
||||
'hash' => Auth::DEFAULT_ALGO,
|
||||
'hashOptions' => Auth::DEFAULT_ALGO_OPTIONS,
|
||||
'passwordUpdate' => 0,
|
||||
'registration' => \time(),
|
||||
'passwordUpdate' => null,
|
||||
'registration' => DateTime::now(),
|
||||
'reset' => false,
|
||||
'name' => $name,
|
||||
'prefs' => new \stdClass(),
|
||||
|
|
@ -506,18 +522,19 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
|
|||
$detector = new Detector($request->getUserAgent('UNKNOWN'));
|
||||
$record = $geodb->get($request->getIP());
|
||||
$secret = Auth::tokenGenerator();
|
||||
$expiry = \time() + Auth::TOKEN_EXPIRATION_LOGIN_LONG;
|
||||
$expire = DateTime::addSeconds(new \DateTime(), Auth::TOKEN_EXPIRATION_LOGIN_LONG);
|
||||
|
||||
$session = new Document(array_merge([
|
||||
'$id' => $dbForProject->getId(),
|
||||
'$id' => ID::unique(),
|
||||
'userId' => $user->getId(),
|
||||
'userInternalId' => $user->getInternalId(),
|
||||
'provider' => $provider,
|
||||
'providerUid' => $oauth2ID,
|
||||
'providerAccessToken' => $accessToken,
|
||||
'providerRefreshToken' => $refreshToken,
|
||||
'providerAccessTokenExpiry' => \time() + (int) $accessTokenExpiry,
|
||||
'providerAccessTokenExpiry' => DateTime::addSeconds(new \DateTime(), (int)$accessTokenExpiry),
|
||||
'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak
|
||||
'expire' => $expiry,
|
||||
'expire' => $expire,
|
||||
'userAgent' => $request->getUserAgent('UNKNOWN'),
|
||||
'ip' => $request->getIP(),
|
||||
'countryCode' => ($record) ? \strtolower($record['country']['iso_code']) : '--',
|
||||
|
|
@ -536,13 +553,15 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
|
|||
->setAttribute('status', true)
|
||||
;
|
||||
|
||||
Authorization::setRole('user:' . $user->getId());
|
||||
Authorization::setRole(Role::user($user->getId())->toString());
|
||||
|
||||
$dbForProject->updateDocument('users', $user->getId(), $user);
|
||||
|
||||
$session = $dbForProject->createDocument('sessions', $session
|
||||
->setAttribute('$read', ['user:' . $user->getId()])
|
||||
->setAttribute('$write', ['user:' . $user->getId()]));
|
||||
$session = $dbForProject->createDocument('sessions', $session->setAttribute('$permissions', [
|
||||
Permission::read(Role::user($user->getId())),
|
||||
Permission::update(Role::user($user->getId())),
|
||||
Permission::delete(Role::user($user->getId())),
|
||||
]));
|
||||
|
||||
$dbForProject->deleteCachedDocument('users', $user->getId());
|
||||
|
||||
|
|
@ -571,8 +590,8 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
|
|||
$response
|
||||
->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
|
||||
->addHeader('Pragma', 'no-cache')
|
||||
->addCookie(Auth::$cookieName . '_legacy', Auth::encodeSession($user->getId(), $secret), $expiry, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null)
|
||||
->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), $expiry, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite'))
|
||||
->addCookie(Auth::$cookieName . '_legacy', Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null)
|
||||
->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite'))
|
||||
->redirect($state['success'])
|
||||
;
|
||||
});
|
||||
|
|
@ -613,7 +632,7 @@ App::post('/v1/account/sessions/magic-url')
|
|||
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
|
||||
$isAppUser = Auth::isAppUser($roles);
|
||||
|
||||
$user = $dbForProject->findOne('users', [new Query('email', Query::TYPE_EQUAL, [$email])]);
|
||||
$user = $dbForProject->findOne('users', [Query::equal('email', [$email])]);
|
||||
|
||||
if (!$user) {
|
||||
$limit = $project->getAttribute('auths', [])['limit'] ?? 0;
|
||||
|
|
@ -626,20 +645,23 @@ App::post('/v1/account/sessions/magic-url')
|
|||
}
|
||||
}
|
||||
|
||||
$userId = $userId == 'unique()' ? $dbForProject->getId() : $userId;
|
||||
$userId = $userId == 'unique()' ? ID::unique() : $userId;
|
||||
|
||||
$user = Authorization::skip(fn () => $dbForProject->createDocument('users', new Document([
|
||||
'$id' => $userId,
|
||||
'$read' => ['role:all'],
|
||||
'$write' => ['user:' . $userId],
|
||||
'$permissions' => [
|
||||
Permission::read(Role::any()),
|
||||
Permission::update(Role::user($userId)),
|
||||
Permission::delete(Role::user($userId)),
|
||||
],
|
||||
'email' => $email,
|
||||
'emailVerification' => false,
|
||||
'status' => true,
|
||||
'password' => null,
|
||||
'hash' => Auth::DEFAULT_ALGO,
|
||||
'hashOptions' => Auth::DEFAULT_ALGO_OPTIONS,
|
||||
'passwordUpdate' => 0,
|
||||
'registration' => \time(),
|
||||
'passwordUpdate' => null,
|
||||
'registration' => DateTime::now(),
|
||||
'reset' => false,
|
||||
'prefs' => new \stdClass(),
|
||||
'sessions' => null,
|
||||
|
|
@ -650,11 +672,10 @@ App::post('/v1/account/sessions/magic-url')
|
|||
}
|
||||
|
||||
$loginSecret = Auth::tokenGenerator();
|
||||
|
||||
$expire = \time() + Auth::TOKEN_EXPIRATION_CONFIRM;
|
||||
$expire = DateTime::addSeconds(new \DateTime(), Auth::TOKEN_EXPIRATION_CONFIRM);
|
||||
|
||||
$token = new Document([
|
||||
'$id' => $dbForProject->getId(),
|
||||
'$id' => ID::unique(),
|
||||
'userId' => $user->getId(),
|
||||
'userInternalId' => $user->getInternalId(),
|
||||
'type' => Auth::TOKEN_TYPE_MAGIC_URL,
|
||||
|
|
@ -664,11 +685,14 @@ App::post('/v1/account/sessions/magic-url')
|
|||
'ip' => $request->getIP(),
|
||||
]);
|
||||
|
||||
Authorization::setRole('user:' . $user->getId());
|
||||
Authorization::setRole(Role::user($user->getId())->toString());
|
||||
|
||||
$token = $dbForProject->createDocument('tokens', $token
|
||||
->setAttribute('$read', ['user:' . $user->getId()])
|
||||
->setAttribute('$write', ['user:' . $user->getId()]));
|
||||
->setAttribute('$permissions', [
|
||||
Permission::read(Role::user($user->getId())),
|
||||
Permission::update(Role::user($user->getId())),
|
||||
Permission::delete(Role::user($user->getId())),
|
||||
]));
|
||||
|
||||
$dbForProject->deleteCachedDocument('users', $user->getId());
|
||||
|
||||
|
|
@ -749,15 +773,16 @@ App::put('/v1/account/sessions/magic-url')
|
|||
$detector = new Detector($request->getUserAgent('UNKNOWN'));
|
||||
$record = $geodb->get($request->getIP());
|
||||
$secret = Auth::tokenGenerator();
|
||||
$expiry = \time() + Auth::TOKEN_EXPIRATION_LOGIN_LONG;
|
||||
$expire = DateTime::addSeconds(new \DateTime(), Auth::TOKEN_EXPIRATION_LOGIN_LONG);
|
||||
|
||||
$session = new Document(array_merge(
|
||||
[
|
||||
'$id' => $dbForProject->getId(),
|
||||
'$id' => ID::unique(),
|
||||
'userId' => $user->getId(),
|
||||
'userInternalId' => $user->getInternalId(),
|
||||
'provider' => Auth::SESSION_PROVIDER_MAGIC_URL,
|
||||
'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak
|
||||
'expire' => $expiry,
|
||||
'expire' => $expire,
|
||||
'userAgent' => $request->getUserAgent('UNKNOWN'),
|
||||
'ip' => $request->getIP(),
|
||||
'countryCode' => ($record) ? \strtolower($record['country']['iso_code']) : '--',
|
||||
|
|
@ -767,11 +792,14 @@ App::put('/v1/account/sessions/magic-url')
|
|||
$detector->getDevice()
|
||||
));
|
||||
|
||||
Authorization::setRole('user:' . $user->getId());
|
||||
Authorization::setRole(Role::user($user->getId())->toString());
|
||||
|
||||
$session = $dbForProject->createDocument('sessions', $session
|
||||
->setAttribute('$read', ['user:' . $user->getId()])
|
||||
->setAttribute('$write', ['user:' . $user->getId()]));
|
||||
->setAttribute('$permissions', [
|
||||
Permission::read(Role::user($user->getId())),
|
||||
Permission::update(Role::user($user->getId())),
|
||||
Permission::delete(Role::user($user->getId())),
|
||||
]));
|
||||
|
||||
$dbForProject->deleteCachedDocument('users', $user->getId());
|
||||
|
||||
|
|
@ -804,8 +832,8 @@ App::put('/v1/account/sessions/magic-url')
|
|||
$protocol = $request->getProtocol();
|
||||
|
||||
$response
|
||||
->addCookie(Auth::$cookieName . '_legacy', Auth::encodeSession($user->getId(), $secret), $expiry, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null)
|
||||
->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), $expiry, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite'))
|
||||
->addCookie(Auth::$cookieName . '_legacy', Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null)
|
||||
->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite'))
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
;
|
||||
|
||||
|
|
@ -853,7 +881,7 @@ App::post('/v1/account/sessions/phone')
|
|||
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
|
||||
$isAppUser = Auth::isAppUser($roles);
|
||||
|
||||
$user = $dbForProject->findOne('users', [new Query('phone', Query::TYPE_EQUAL, [$phone])]);
|
||||
$user = $dbForProject->findOne('users', [Query::equal('phone', [$phone])]);
|
||||
|
||||
if (!$user) {
|
||||
$limit = $project->getAttribute('auths', [])['limit'] ?? 0;
|
||||
|
|
@ -866,20 +894,23 @@ App::post('/v1/account/sessions/phone')
|
|||
}
|
||||
}
|
||||
|
||||
$userId = $userId == 'unique()' ? $dbForProject->getId() : $userId;
|
||||
$userId = $userId == 'unique()' ? ID::unique() : $userId;
|
||||
|
||||
$user = Authorization::skip(fn () => $dbForProject->createDocument('users', new Document([
|
||||
'$id' => $userId,
|
||||
'$read' => ['role:all'],
|
||||
'$write' => ['user:' . $userId],
|
||||
'$permissions' => [
|
||||
Permission::read(Role::any()),
|
||||
Permission::update(Role::user($userId)),
|
||||
Permission::delete(Role::user($userId)),
|
||||
],
|
||||
'email' => null,
|
||||
'phone' => $phone,
|
||||
'emailVerification' => false,
|
||||
'phoneVerification' => false,
|
||||
'status' => true,
|
||||
'password' => null,
|
||||
'passwordUpdate' => 0,
|
||||
'registration' => \time(),
|
||||
'passwordUpdate' => null,
|
||||
'registration' => DateTime::now(),
|
||||
'reset' => false,
|
||||
'prefs' => new \stdClass(),
|
||||
'sessions' => null,
|
||||
|
|
@ -890,11 +921,10 @@ App::post('/v1/account/sessions/phone')
|
|||
}
|
||||
|
||||
$secret = (App::getEnv('_APP_SMS_PROVIDER') === 'sms://mock') ? Mock::$digits : Auth::codeGenerator();
|
||||
|
||||
$expire = \time() + Auth::TOKEN_EXPIRATION_PHONE;
|
||||
$expire = DateTime::addSeconds(new \DateTime(), Auth::TOKEN_EXPIRATION_PHONE);
|
||||
|
||||
$token = new Document([
|
||||
'$id' => $dbForProject->getId(),
|
||||
'$id' => ID::unique(),
|
||||
'userId' => $user->getId(),
|
||||
'userInternalId' => $user->getInternalId(),
|
||||
'type' => Auth::TOKEN_TYPE_PHONE,
|
||||
|
|
@ -904,11 +934,14 @@ App::post('/v1/account/sessions/phone')
|
|||
'ip' => $request->getIP(),
|
||||
]);
|
||||
|
||||
Authorization::setRole('user:' . $user->getId());
|
||||
Authorization::setRole(Role::user($user->getId())->toString());
|
||||
|
||||
$token = $dbForProject->createDocument('tokens', $token
|
||||
->setAttribute('$read', ['user:' . $user->getId()])
|
||||
->setAttribute('$write', ['user:' . $user->getId()]));
|
||||
->setAttribute('$permissions', [
|
||||
Permission::read(Role::user($user->getId())),
|
||||
Permission::update(Role::user($user->getId())),
|
||||
Permission::delete(Role::user($user->getId())),
|
||||
]));
|
||||
|
||||
$dbForProject->deleteCachedDocument('users', $user->getId());
|
||||
|
||||
|
|
@ -974,15 +1007,16 @@ App::put('/v1/account/sessions/phone')
|
|||
$detector = new Detector($request->getUserAgent('UNKNOWN'));
|
||||
$record = $geodb->get($request->getIP());
|
||||
$secret = Auth::tokenGenerator();
|
||||
$expiry = \time() + Auth::TOKEN_EXPIRATION_LOGIN_LONG;
|
||||
$expire = DateTime::addSeconds(new \DateTime(), Auth::TOKEN_EXPIRATION_LOGIN_LONG);
|
||||
|
||||
$session = new Document(array_merge(
|
||||
[
|
||||
'$id' => $dbForProject->getId(),
|
||||
'$id' => ID::unique(),
|
||||
'userId' => $user->getId(),
|
||||
'userInternalId' => $user->getInternalId(),
|
||||
'provider' => Auth::SESSION_PROVIDER_PHONE,
|
||||
'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak
|
||||
'expire' => $expiry,
|
||||
'expire' => $expire,
|
||||
'userAgent' => $request->getUserAgent('UNKNOWN'),
|
||||
'ip' => $request->getIP(),
|
||||
'countryCode' => ($record) ? \strtolower($record['country']['iso_code']) : '--',
|
||||
|
|
@ -992,11 +1026,14 @@ App::put('/v1/account/sessions/phone')
|
|||
$detector->getDevice()
|
||||
));
|
||||
|
||||
Authorization::setRole('user:' . $user->getId());
|
||||
Authorization::setRole(Role::user($user->getId())->toString());
|
||||
|
||||
$session = $dbForProject->createDocument('sessions', $session
|
||||
->setAttribute('$read', ['user:' . $user->getId()])
|
||||
->setAttribute('$write', ['user:' . $user->getId()]));
|
||||
->setAttribute('$permissions', [
|
||||
Permission::read(Role::user($user->getId())),
|
||||
Permission::update(Role::user($user->getId())),
|
||||
Permission::delete(Role::user($user->getId())),
|
||||
]));
|
||||
|
||||
$dbForProject->deleteCachedDocument('users', $user->getId());
|
||||
|
||||
|
|
@ -1027,8 +1064,8 @@ App::put('/v1/account/sessions/phone')
|
|||
$protocol = $request->getProtocol();
|
||||
|
||||
$response
|
||||
->addCookie(Auth::$cookieName . '_legacy', Auth::encodeSession($user->getId(), $secret), $expiry, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null)
|
||||
->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), $expiry, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite'))
|
||||
->addCookie(Auth::$cookieName . '_legacy', Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null)
|
||||
->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite'))
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
;
|
||||
|
||||
|
|
@ -1091,19 +1128,22 @@ App::post('/v1/account/sessions/anonymous')
|
|||
}
|
||||
}
|
||||
|
||||
$userId = $dbForProject->getId();
|
||||
$userId = ID::unique();
|
||||
$user = Authorization::skip(fn() => $dbForProject->createDocument('users', new Document([
|
||||
'$id' => $userId,
|
||||
'$read' => ['role:all'],
|
||||
'$write' => ['user:' . $userId],
|
||||
'$permissions' => [
|
||||
Permission::read(Role::any()),
|
||||
Permission::update(Role::user($userId)),
|
||||
Permission::delete(Role::user($userId)),
|
||||
],
|
||||
'email' => null,
|
||||
'emailVerification' => false,
|
||||
'status' => true,
|
||||
'password' => null,
|
||||
'hash' => Auth::DEFAULT_ALGO,
|
||||
'hashOptions' => Auth::DEFAULT_ALGO_OPTIONS,
|
||||
'passwordUpdate' => 0,
|
||||
'registration' => \time(),
|
||||
'passwordUpdate' => null,
|
||||
'registration' => DateTime::now(),
|
||||
'reset' => false,
|
||||
'name' => null,
|
||||
'prefs' => new \stdClass(),
|
||||
|
|
@ -1118,15 +1158,16 @@ App::post('/v1/account/sessions/anonymous')
|
|||
$detector = new Detector($request->getUserAgent('UNKNOWN'));
|
||||
$record = $geodb->get($request->getIP());
|
||||
$secret = Auth::tokenGenerator();
|
||||
$expiry = \time() + Auth::TOKEN_EXPIRATION_LOGIN_LONG;
|
||||
$expire = DateTime::addSeconds(new \DateTime(), Auth::TOKEN_EXPIRATION_LOGIN_LONG);
|
||||
|
||||
$session = new Document(array_merge(
|
||||
[
|
||||
'$id' => $dbForProject->getId(),
|
||||
'$id' => ID::unique(),
|
||||
'userId' => $user->getId(),
|
||||
'userInternalId' => $user->getInternalId(),
|
||||
'provider' => Auth::SESSION_PROVIDER_ANONYMOUS,
|
||||
'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak
|
||||
'expire' => $expiry,
|
||||
'expire' => $expire,
|
||||
'userAgent' => $request->getUserAgent('UNKNOWN'),
|
||||
'ip' => $request->getIP(),
|
||||
'countryCode' => ($record) ? \strtolower($record['country']['iso_code']) : '--',
|
||||
|
|
@ -1136,11 +1177,13 @@ App::post('/v1/account/sessions/anonymous')
|
|||
$detector->getDevice()
|
||||
));
|
||||
|
||||
Authorization::setRole('user:' . $user->getId());
|
||||
Authorization::setRole(Role::user($user->getId())->toString());
|
||||
|
||||
$session = $dbForProject->createDocument('sessions', $session
|
||||
->setAttribute('$read', ['user:' . $user->getId()])
|
||||
->setAttribute('$write', ['user:' . $user->getId()]));
|
||||
$session = $dbForProject->createDocument('sessions', $session-> setAttribute('$permissions', [
|
||||
Permission::read(Role::user($user->getId())),
|
||||
Permission::update(Role::user($user->getId())),
|
||||
Permission::delete(Role::user($user->getId())),
|
||||
]));
|
||||
|
||||
$dbForProject->deleteCachedDocument('users', $user->getId());
|
||||
|
||||
|
|
@ -1154,8 +1197,8 @@ App::post('/v1/account/sessions/anonymous')
|
|||
}
|
||||
|
||||
$response
|
||||
->addCookie(Auth::$cookieName . '_legacy', Auth::encodeSession($user->getId(), $secret), $expiry, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null)
|
||||
->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), $expiry, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite'))
|
||||
->addCookie(Auth::$cookieName . '_legacy', Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null)
|
||||
->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite'))
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
;
|
||||
|
||||
|
|
@ -1441,7 +1484,7 @@ App::patch('/v1/account/password')
|
|||
->action(function (string $password, string $oldPassword, Response $response, Document $user, Database $dbForProject, Event $events) {
|
||||
|
||||
// Check old password only if its an existing user.
|
||||
if ($user->getAttribute('passwordUpdate') !== 0 && !Auth::passwordVerify($oldPassword, $user->getAttribute('password'), $user->getAttribute('hash'), $user->getAttribute('hashOptions'))) { // Double check user password
|
||||
if (!empty($user->getAttribute('passwordUpdate')) && !Auth::passwordVerify($oldPassword, $user->getAttribute('password'), $user->getAttribute('hash'), $user->getAttribute('hashOptions'))) { // Double check user password
|
||||
throw new Exception(Exception::USER_INVALID_CREDENTIALS);
|
||||
}
|
||||
|
||||
|
|
@ -1449,7 +1492,7 @@ App::patch('/v1/account/password')
|
|||
->setAttribute('password', Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS))
|
||||
->setAttribute('hash', Auth::DEFAULT_ALGO)
|
||||
->setAttribute('hashOptions', Auth::DEFAULT_ALGO_OPTIONS)
|
||||
->setAttribute('passwordUpdate', \time()));
|
||||
->setAttribute('passwordUpdate', DateTime::now()));
|
||||
|
||||
$events->setParam('userId', $user->getId());
|
||||
|
||||
|
|
@ -1748,7 +1791,7 @@ App::patch('/v1/account/sessions/:sessionId')
|
|||
$session
|
||||
->setAttribute('providerAccessToken', $oauth2->getAccessToken(''))
|
||||
->setAttribute('providerRefreshToken', $oauth2->getRefreshToken(''))
|
||||
->setAttribute('providerAccessTokenExpiry', \time() + (int) $oauth2->getAccessTokenExpiry(''));
|
||||
->setAttribute('providerAccessTokenExpiry', DateTime::addSeconds(new \DateTime(), (int)$oauth2->getAccessTokenExpiry('')));
|
||||
|
||||
$dbForProject->updateDocument('sessions', $sessionId, $session);
|
||||
|
||||
|
|
@ -1867,7 +1910,7 @@ App::post('/v1/account/recovery')
|
|||
$email = \strtolower($email);
|
||||
|
||||
$profile = $dbForProject->findOne('users', [
|
||||
new Query('email', Query::TYPE_EQUAL, [$email])
|
||||
Query::equal('email', [$email]),
|
||||
]);
|
||||
|
||||
if (!$profile) {
|
||||
|
|
@ -1878,11 +1921,11 @@ App::post('/v1/account/recovery')
|
|||
throw new Exception(Exception::USER_BLOCKED);
|
||||
}
|
||||
|
||||
$expire = \time() + Auth::TOKEN_EXPIRATION_RECOVERY;
|
||||
$expire = DateTime::addSeconds(new \DateTime(), Auth::TOKEN_EXPIRATION_RECOVERY);
|
||||
|
||||
$secret = Auth::tokenGenerator();
|
||||
$recovery = new Document([
|
||||
'$id' => $dbForProject->getId(),
|
||||
'$id' => ID::unique(),
|
||||
'userId' => $profile->getId(),
|
||||
'userInternalId' => $profile->getInternalId(),
|
||||
'type' => Auth::TOKEN_TYPE_RECOVERY,
|
||||
|
|
@ -1892,11 +1935,14 @@ App::post('/v1/account/recovery')
|
|||
'ip' => $request->getIP(),
|
||||
]);
|
||||
|
||||
Authorization::setRole('user:' . $profile->getId());
|
||||
Authorization::setRole(Role::user($profile->getId())->toString());
|
||||
|
||||
$recovery = $dbForProject->createDocument('tokens', $recovery
|
||||
->setAttribute('$read', ['user:' . $profile->getId()])
|
||||
->setAttribute('$write', ['user:' . $profile->getId()]));
|
||||
->setAttribute('$permissions', [
|
||||
Permission::read(Role::user($profile->getId())),
|
||||
Permission::update(Role::user($profile->getId())),
|
||||
Permission::delete(Role::user($profile->getId())),
|
||||
]));
|
||||
|
||||
$dbForProject->deleteCachedDocument('users', $profile->getId());
|
||||
|
||||
|
|
@ -1972,13 +2018,13 @@ App::put('/v1/account/recovery')
|
|||
throw new Exception(Exception::USER_INVALID_TOKEN);
|
||||
}
|
||||
|
||||
Authorization::setRole('user:' . $profile->getId());
|
||||
Authorization::setRole(Role::user($profile->getId())->toString());
|
||||
|
||||
$profile = $dbForProject->updateDocument('users', $profile->getId(), $profile
|
||||
->setAttribute('password', Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS))
|
||||
->setAttribute('hash', Auth::DEFAULT_ALGO)
|
||||
->setAttribute('hashOptions', Auth::DEFAULT_ALGO_OPTIONS)
|
||||
->setAttribute('passwordUpdate', \time())
|
||||
->setAttribute('passwordUpdate', DateTime::now())
|
||||
->setAttribute('emailVerification', true));
|
||||
|
||||
$recoveryDocument = $dbForProject->getDocument('tokens', $recovery);
|
||||
|
|
@ -2032,13 +2078,11 @@ App::post('/v1/account/verification')
|
|||
$roles = Authorization::getRoles();
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
|
||||
$isAppUser = Auth::isAppUser($roles);
|
||||
|
||||
$verificationSecret = Auth::tokenGenerator();
|
||||
|
||||
$expire = \time() + Auth::TOKEN_EXPIRATION_CONFIRM;
|
||||
$expire = DateTime::addSeconds(new \DateTime(), Auth::TOKEN_EXPIRATION_CONFIRM);
|
||||
|
||||
$verification = new Document([
|
||||
'$id' => $dbForProject->getId(),
|
||||
'$id' => ID::unique(),
|
||||
'userId' => $user->getId(),
|
||||
'userInternalId' => $user->getInternalId(),
|
||||
'type' => Auth::TOKEN_TYPE_VERIFICATION,
|
||||
|
|
@ -2048,11 +2092,14 @@ App::post('/v1/account/verification')
|
|||
'ip' => $request->getIP(),
|
||||
]);
|
||||
|
||||
Authorization::setRole('user:' . $user->getId());
|
||||
Authorization::setRole(Role::user($user->getId())->toString());
|
||||
|
||||
$verification = $dbForProject->createDocument('tokens', $verification
|
||||
->setAttribute('$read', ['user:' . $user->getId()])
|
||||
->setAttribute('$write', ['user:' . $user->getId()]));
|
||||
->setAttribute('$permissions', [
|
||||
Permission::read(Role::user($user->getId())),
|
||||
Permission::update(Role::user($user->getId())),
|
||||
Permission::delete(Role::user($user->getId())),
|
||||
]));
|
||||
|
||||
$dbForProject->deleteCachedDocument('users', $user->getId());
|
||||
|
||||
|
|
@ -2122,7 +2169,7 @@ App::put('/v1/account/verification')
|
|||
throw new Exception(Exception::USER_INVALID_TOKEN);
|
||||
}
|
||||
|
||||
Authorization::setRole('user:' . $profile->getId());
|
||||
Authorization::setRole(Role::user($profile->getId())->toString());
|
||||
|
||||
$profile = $dbForProject->updateDocument('users', $profile->getId(), $profile->setAttribute('emailVerification', true));
|
||||
|
||||
|
|
@ -2178,14 +2225,12 @@ App::post('/v1/account/verification/phone')
|
|||
$roles = Authorization::getRoles();
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
|
||||
$isAppUser = Auth::isAppUser($roles);
|
||||
|
||||
$verificationSecret = Auth::tokenGenerator();
|
||||
|
||||
$secret = (App::getEnv('_APP_SMS_PROVIDER') === 'sms://mock') ? Mock::$digits : Auth::codeGenerator();
|
||||
$expire = \time() + Auth::TOKEN_EXPIRATION_CONFIRM;
|
||||
$expire = DateTime::addSeconds(new \DateTime(), Auth::TOKEN_EXPIRATION_CONFIRM);
|
||||
|
||||
$verification = new Document([
|
||||
'$id' => $dbForProject->getId(),
|
||||
'$id' => ID::unique(),
|
||||
'userId' => $user->getId(),
|
||||
'userInternalId' => $user->getInternalId(),
|
||||
'type' => Auth::TOKEN_TYPE_PHONE,
|
||||
|
|
@ -2195,11 +2240,14 @@ App::post('/v1/account/verification/phone')
|
|||
'ip' => $request->getIP(),
|
||||
]);
|
||||
|
||||
Authorization::setRole('user:' . $user->getId());
|
||||
Authorization::setRole(Role::user($user->getId())->toString());
|
||||
|
||||
$verification = $dbForProject->createDocument('tokens', $verification
|
||||
->setAttribute('$read', ['user:' . $user->getId()])
|
||||
->setAttribute('$write', ['user:' . $user->getId()]));
|
||||
->setAttribute('$permissions', [
|
||||
Permission::read(Role::user($user->getId())),
|
||||
Permission::update(Role::user($user->getId())),
|
||||
Permission::delete(Role::user($user->getId())),
|
||||
]));
|
||||
|
||||
$dbForProject->deleteCachedDocument('users', $user->getId());
|
||||
|
||||
|
|
@ -2261,7 +2309,7 @@ App::put('/v1/account/verification/phone')
|
|||
throw new Exception(Exception::USER_INVALID_TOKEN);
|
||||
}
|
||||
|
||||
Authorization::setRole('user:' . $profile->getId());
|
||||
Authorization::setRole(Role::user($profile->getId())->toString());
|
||||
|
||||
$profile = $dbForProject->updateDocument('users', $profile->getId(), $profile->setAttribute('phoneVerification', true));
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -9,6 +9,9 @@ use Appwrite\Event\Func;
|
|||
use Appwrite\Event\Validator\Event as ValidatorEvent;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Utopia\Database\Validator\CustomId;
|
||||
use Utopia\Database\ID;
|
||||
use Utopia\Database\Permission;
|
||||
use Utopia\Database\Role;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Appwrite\Usage\Stats;
|
||||
use Utopia\Storage\Device;
|
||||
|
|
@ -22,6 +25,7 @@ use Appwrite\Task\Validator\Cron;
|
|||
use Utopia\App;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Validator\ArrayList;
|
||||
|
|
@ -33,7 +37,7 @@ use Utopia\Config\Config;
|
|||
use Cron\CronExpression;
|
||||
use Executor\Executor;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Database\Validator\Permissions;
|
||||
use Utopia\Database\Validator\Roles;
|
||||
use Utopia\Validator\Boolean;
|
||||
|
||||
include_once __DIR__ . '/../shared/api.php';
|
||||
|
|
@ -53,7 +57,7 @@ App::post('/v1/functions')
|
|||
->label('sdk.response.model', Response::MODEL_FUNCTION)
|
||||
->param('functionId', '', new CustomId(), 'Function ID. Choose your own unique ID or pass the string "unique()" to auto generate it. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('name', '', new Text(128), 'Function name. Max length: 128 chars.')
|
||||
->param('execute', [], new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of strings with execution permissions. By default no user is granted with any execute permissions. [learn more about permissions](https://appwrite.io/docs/permissions) and get a full list of available permissions. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed, each 64 characters long.')
|
||||
->param('execute', [], new Roles(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of strings with execution roles. By default no user is granted with any execute permissions. [learn more about permissions](https://appwrite.io/docs/permissions). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 64 characters long.')
|
||||
->param('runtime', '', new WhiteList(array_keys(Config::getParam('runtimes')), true), 'Execution runtime.')
|
||||
->param('vars', [], new Assoc(), 'Key-value JSON object that will be passed to the function as environment variables.', true)
|
||||
->param('events', [], new ArrayList(new ValidatorEvent(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Events list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' events are allowed.', true)
|
||||
|
|
@ -64,7 +68,7 @@ App::post('/v1/functions')
|
|||
->inject('events')
|
||||
->action(function (string $functionId, string $name, array $execute, string $runtime, array $vars, array $events, string $schedule, int $timeout, Response $response, Database $dbForProject, Event $eventsInstance) {
|
||||
|
||||
$functionId = ($functionId == 'unique()') ? $dbForProject->getId() : $functionId;
|
||||
$functionId = ($functionId == 'unique()') ? ID::unique() : $functionId;
|
||||
$function = $dbForProject->createDocument('functions', new Document([
|
||||
'$id' => $functionId,
|
||||
'execute' => $execute,
|
||||
|
|
@ -75,8 +79,8 @@ App::post('/v1/functions')
|
|||
'vars' => $vars,
|
||||
'events' => $events,
|
||||
'schedule' => $schedule,
|
||||
'schedulePrevious' => 0,
|
||||
'scheduleNext' => 0,
|
||||
'schedulePrevious' => null,
|
||||
'scheduleNext' => null,
|
||||
'timeout' => $timeout,
|
||||
'search' => implode(' ', [$functionId, $name, $runtime]),
|
||||
]));
|
||||
|
|
@ -103,28 +107,34 @@ App::get('/v1/functions')
|
|||
->param('offset', 0, new Range(0, APP_LIMIT_COUNT), 'Offset value. The default value is 0. Use this value to manage pagination. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
|
||||
->param('cursor', '', new UID(), 'ID of the function used as the starting point for the query, excluding the function itself. Should be used for efficient pagination when working with large sets of data. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
|
||||
->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor, can be either \'before\' or \'after\'.', true)
|
||||
->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true)
|
||||
->param('orderType', Database::ORDER_ASC, new WhiteList([Database::ORDER_ASC, Database::ORDER_DESC], true), 'Order result by ' . Database::ORDER_ASC . ' or ' . Database::ORDER_DESC . ' order.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->action(function (string $search, int $limit, int $offset, string $cursor, string $cursorDirection, string $orderType, Response $response, Database $dbForProject) {
|
||||
|
||||
if (!empty($cursor)) {
|
||||
$cursorFunction = $dbForProject->getDocument('functions', $cursor);
|
||||
$filterQueries = [];
|
||||
|
||||
if ($cursorFunction->isEmpty()) {
|
||||
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Function '{$cursor}' for the 'cursor' value not found.");
|
||||
}
|
||||
if (!empty($search)) {
|
||||
$filterQueries[] = Query::search('search', $search);
|
||||
}
|
||||
|
||||
$queries = [];
|
||||
$queries[] = Query::limit($limit);
|
||||
$queries[] = Query::offset($offset);
|
||||
$queries[] = $orderType === Database::ORDER_ASC ? Query::orderAsc('') : Query::orderDesc('');
|
||||
if (!empty($cursor)) {
|
||||
$cursorDocument = $dbForProject->getDocument('functions', $cursor);
|
||||
|
||||
if (!empty($search)) {
|
||||
$queries[] = new Query('search', Query::TYPE_SEARCH, [$search]);
|
||||
if ($cursorDocument->isEmpty()) {
|
||||
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Function '{$cursor}' for the 'cursor' value not found.");
|
||||
}
|
||||
|
||||
$queries[] = $cursorDirection === Database::CURSOR_AFTER ? Query::cursorAfter($cursorDocument) : Query::cursorBefore($cursorDocument);
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'functions' => $dbForProject->find('functions', $queries, $limit, $offset, [], [$orderType], $cursorFunction ?? null, $cursorDirection),
|
||||
'total' => $dbForProject->count('functions', $queries, APP_LIMIT_COUNT),
|
||||
'functions' => $dbForProject->find('functions', \array_merge($filterQueries, $queries)),
|
||||
'total' => $dbForProject->count('functions', $filterQueries, APP_LIMIT_COUNT),
|
||||
]), Response::MODEL_FUNCTION_LIST);
|
||||
});
|
||||
|
||||
|
|
@ -241,9 +251,11 @@ App::get('/v1/functions/:functionId/usage')
|
|||
$period = $periods[$range]['period'];
|
||||
|
||||
$requestDocs = $dbForProject->find('stats', [
|
||||
new Query('period', Query::TYPE_EQUAL, [$period]),
|
||||
new Query('metric', Query::TYPE_EQUAL, [$metric]),
|
||||
], $limit, 0, ['time'], [Database::ORDER_DESC]);
|
||||
Query::equal('period', [$period]),
|
||||
Query::equal('metric', [$metric]),
|
||||
Query::limit($limit),
|
||||
Query::orderDesc('time'),
|
||||
]);
|
||||
|
||||
$stats[$metric] = [];
|
||||
foreach ($requestDocs as $requestDoc) {
|
||||
|
|
@ -263,7 +275,7 @@ App::get('/v1/functions/:functionId/usage')
|
|||
};
|
||||
$stats[$metric][] = [
|
||||
'value' => 0,
|
||||
'date' => ($stats[$metric][$last]['date'] ?? \time()) - $diff, // time of last metric minus period
|
||||
'date' => DateTime::addSeconds(new \DateTime($stats[$metric][$last]['date'] ?? null), -1 * $diff),
|
||||
];
|
||||
$backfill--;
|
||||
}
|
||||
|
|
@ -342,8 +354,8 @@ App::get('/v1/functions/usage')
|
|||
$period = $periods[$range]['period'];
|
||||
|
||||
$requestDocs = $dbForProject->find('stats', [
|
||||
new Query('period', Query::TYPE_EQUAL, [$period]),
|
||||
new Query('metric', Query::TYPE_EQUAL, [$metric]),
|
||||
Query::equal('period', [$period]),
|
||||
Query::equal('metric', [$metric]),
|
||||
], $limit, 0, ['time'], [Database::ORDER_DESC]);
|
||||
|
||||
$stats[$metric] = [];
|
||||
|
|
@ -403,7 +415,7 @@ App::put('/v1/functions/:functionId')
|
|||
->label('sdk.response.model', Response::MODEL_FUNCTION)
|
||||
->param('functionId', '', new UID(), 'Function ID.')
|
||||
->param('name', '', new Text(128), 'Function name. Max length: 128 chars.')
|
||||
->param('execute', [], new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of strings with execution permissions. By default no user is granted with any execute permissions. [learn more about permissions](https://appwrite.io/docs/permissions) and get a full list of available permissions. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed, each 64 characters long.')
|
||||
->param('execute', [], new Roles(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of strings with execution roles. By default no user is granted with any execute permissions. [learn more about permissions](https://appwrite.io/docs/permissions). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 64 characters long.')
|
||||
->param('vars', [], new Assoc(), 'Key-value JSON object that will be passed to the function as environment variables.', true)
|
||||
->param('events', [], new ArrayList(new ValidatorEvent(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Events list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' events are allowed.', true)
|
||||
->param('schedule', '', new Cron(), 'Schedule CRON syntax.', true)
|
||||
|
|
@ -422,8 +434,8 @@ App::put('/v1/functions/:functionId')
|
|||
}
|
||||
|
||||
$original = $function->getAttribute('schedule', '');
|
||||
$cron = (!empty($function->getAttribute('deployment', null)) && !empty($schedule)) ? new CronExpression($schedule) : null;
|
||||
$next = (!empty($function->getAttribute('deployment', null)) && !empty($schedule)) ? $cron->getNextRunDate()->format('U') : 0;
|
||||
$cron = (!empty($function->getAttribute('deployment')) && !empty($schedule)) ? new CronExpression($schedule) : null;
|
||||
$next = (!empty($function->getAttribute('deployment')) && !empty($schedule)) ? DateTime::format($cron->getNextRunDate()) : null;
|
||||
|
||||
$function = $dbForProject->updateDocument('functions', $function->getId(), new Document(array_merge($function->getArrayCopy(), [
|
||||
'execute' => $execute,
|
||||
|
|
@ -431,7 +443,7 @@ App::put('/v1/functions/:functionId')
|
|||
'vars' => $vars,
|
||||
'events' => $events,
|
||||
'schedule' => $schedule,
|
||||
'scheduleNext' => (int)$next,
|
||||
'scheduleNext' => $next,
|
||||
'timeout' => $timeout,
|
||||
'search' => implode(' ', [$functionId, $name, $function->getAttribute('runtime')]),
|
||||
])));
|
||||
|
|
@ -443,9 +455,8 @@ App::put('/v1/functions/:functionId')
|
|||
->setFunction($function)
|
||||
->setType('schedule')
|
||||
->setUser($user)
|
||||
->setProject($project);
|
||||
|
||||
$functionEvent->schedule($next);
|
||||
->setProject($project)
|
||||
->schedule(new \DateTime($next));
|
||||
}
|
||||
|
||||
$eventsInstance->setParam('functionId', $function->getId());
|
||||
|
|
@ -496,11 +507,11 @@ App::patch('/v1/functions/:functionId/deployments/:deploymentId')
|
|||
|
||||
$schedule = $function->getAttribute('schedule', '');
|
||||
$cron = (empty($function->getAttribute('deployment')) && !empty($schedule)) ? new CronExpression($schedule) : null;
|
||||
$next = (empty($function->getAttribute('deployment')) && !empty($schedule)) ? $cron->getNextRunDate()->format('U') : 0;
|
||||
$next = (empty($function->getAttribute('deployment')) && !empty($schedule)) ? DateTime::format($cron->getNextRunDate()) : null;
|
||||
|
||||
$function = $dbForProject->updateDocument('functions', $function->getId(), new Document(array_merge($function->getArrayCopy(), [
|
||||
'deployment' => $deployment->getId(),
|
||||
'scheduleNext' => (int)$next,
|
||||
'scheduleNext' => $next,
|
||||
])));
|
||||
|
||||
if ($next) { // Init first schedule
|
||||
|
|
@ -508,8 +519,8 @@ App::patch('/v1/functions/:functionId/deployments/:deploymentId')
|
|||
$functionEvent
|
||||
->setType('schedule')
|
||||
->setFunction($function)
|
||||
->setProject($project);
|
||||
$functionEvent->schedule($next);
|
||||
->setProject($project)
|
||||
->schedule(new \DateTime($next));
|
||||
}
|
||||
|
||||
$events
|
||||
|
|
@ -610,7 +621,7 @@ App::post('/v1/functions/:functionId/deployments')
|
|||
}
|
||||
|
||||
$contentRange = $request->getHeader('content-range');
|
||||
$deploymentId = $dbForProject->getId();
|
||||
$deploymentId = ID::unique();
|
||||
$chunk = 1;
|
||||
$chunks = 1;
|
||||
|
||||
|
|
@ -668,9 +679,9 @@ App::post('/v1/functions/:functionId/deployments')
|
|||
if ($activate) {
|
||||
// Remove deploy for all other deployments.
|
||||
$activeDeployments = $dbForProject->find('deployments', [
|
||||
new Query('activate', Query::TYPE_EQUAL, [true]),
|
||||
new Query('resourceId', Query::TYPE_EQUAL, [$functionId]),
|
||||
new Query('resourceType', Query::TYPE_EQUAL, ['functions'])
|
||||
Query::equal('activate', [true]),
|
||||
Query::equal('resourceId', [$functionId]),
|
||||
Query::equal('resourceType', ['functions'])
|
||||
]);
|
||||
|
||||
foreach ($activeDeployments as $activeDeployment) {
|
||||
|
|
@ -684,8 +695,11 @@ App::post('/v1/functions/:functionId/deployments')
|
|||
if ($deployment->isEmpty()) {
|
||||
$deployment = $dbForProject->createDocument('deployments', new Document([
|
||||
'$id' => $deploymentId,
|
||||
'$read' => ['role:all'],
|
||||
'$write' => ['role:all'],
|
||||
'$permissions' => [
|
||||
Permission::read(Role::any()),
|
||||
Permission::update(Role::any()),
|
||||
Permission::delete(Role::any()),
|
||||
],
|
||||
'resourceId' => $function->getId(),
|
||||
'resourceType' => 'functions',
|
||||
'entrypoint' => $entrypoint,
|
||||
|
|
@ -711,8 +725,11 @@ App::post('/v1/functions/:functionId/deployments')
|
|||
if ($deployment->isEmpty()) {
|
||||
$deployment = $dbForProject->createDocument('deployments', new Document([
|
||||
'$id' => $deploymentId,
|
||||
'$read' => ['role:all'],
|
||||
'$write' => ['role:all'],
|
||||
'$permissions' => [
|
||||
Permission::read(Role::any()),
|
||||
Permission::update(Role::any()),
|
||||
Permission::delete(Role::any()),
|
||||
],
|
||||
'resourceId' => $function->getId(),
|
||||
'resourceType' => 'functions',
|
||||
'entrypoint' => $entrypoint,
|
||||
|
|
@ -756,7 +773,7 @@ App::get('/v1/functions/:functionId/deployments')
|
|||
->param('offset', 0, new Range(0, APP_LIMIT_COUNT), 'Offset value. The default value is 0. Use this value to manage pagination. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
|
||||
->param('cursor', '', new UID(), 'ID of the deployment used as the starting point for the query, excluding the deployment itself. Should be used for efficient pagination when working with large sets of data. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
|
||||
->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor, can be either \'before\' or \'after\'.', true)
|
||||
->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true)
|
||||
->param('orderType', Database::ORDER_ASC, new WhiteList([Database::ORDER_ASC, Database::ORDER_DESC], true), 'Order result by ' . Database::ORDER_ASC . ' or ' . Database::ORDER_DESC . ' order.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->action(function (string $functionId, string $search, int $limit, int $offset, string $cursor, string $cursorDirection, string $orderType, Response $response, Database $dbForProject) {
|
||||
|
|
@ -767,25 +784,31 @@ App::get('/v1/functions/:functionId/deployments')
|
|||
throw new Exception(Exception::FUNCTION_NOT_FOUND);
|
||||
}
|
||||
|
||||
if (!empty($cursor)) {
|
||||
$cursorDeployment = $dbForProject->getDocument('deployments', $cursor);
|
||||
|
||||
if ($cursorDeployment->isEmpty()) {
|
||||
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Tag '{$cursor}' for the 'cursor' value not found.");
|
||||
}
|
||||
}
|
||||
|
||||
$queries = [];
|
||||
$filterQueries = [];
|
||||
|
||||
if (!empty($search)) {
|
||||
$queries[] = new Query('search', Query::TYPE_SEARCH, [$search]);
|
||||
$filterQueries[] = Query::search('search', $search);
|
||||
}
|
||||
|
||||
$queries[] = new Query('resourceId', Query::TYPE_EQUAL, [$function->getId()]);
|
||||
$queries[] = new Query('resourceType', Query::TYPE_EQUAL, ['functions']);
|
||||
$filterQueries[] = Query::equal('resourceId', [$function->getId()]);
|
||||
$filterQueries[] = Query::equal('resourceType', ['functions']);
|
||||
|
||||
$results = $dbForProject->find('deployments', $queries, $limit, $offset, [], [$orderType], $cursorDeployment ?? null, $cursorDirection);
|
||||
$total = $dbForProject->count('deployments', $queries, APP_LIMIT_COUNT);
|
||||
$queries = [];
|
||||
$queries[] = Query::limit($limit);
|
||||
$queries[] = Query::offset($offset);
|
||||
$queries[] = $orderType === Database::ORDER_ASC ? Query::orderAsc('') : Query::orderDesc('');
|
||||
if (!empty($cursor)) {
|
||||
$cursorDocument = $dbForProject->getDocument('deployments', $cursor);
|
||||
|
||||
if ($cursorDocument->isEmpty()) {
|
||||
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Tag '{$cursor}' for the 'cursor' value not found.");
|
||||
}
|
||||
|
||||
$queries[] = $cursorDirection === Database::CURSOR_AFTER ? Query::cursorAfter($cursorDocument) : Query::cursorBefore($cursorDocument);
|
||||
}
|
||||
|
||||
$results = $dbForProject->find('deployments', \array_merge($filterQueries, $queries));
|
||||
$total = $dbForProject->count('deployments', $filterQueries, APP_LIMIT_COUNT);
|
||||
|
||||
foreach ($results as $result) {
|
||||
$build = $dbForProject->getDocument('builds', $result->getAttribute('buildId', ''));
|
||||
|
|
@ -959,13 +982,12 @@ App::post('/v1/functions/:functionId/executions')
|
|||
throw new Exception(Exception::USER_UNAUTHORIZED, $validator->getDescription());
|
||||
}
|
||||
|
||||
$executionId = $dbForProject->getId();
|
||||
$executionId = ID::unique();
|
||||
|
||||
/** @var Document $execution */
|
||||
$execution = Authorization::skip(fn () => $dbForProject->createDocument('executions', new Document([
|
||||
'$id' => $executionId,
|
||||
'$read' => (!$user->isEmpty()) ? ['user:' . $user->getId()] : [],
|
||||
'$write' => [],
|
||||
'$permissions' => !$user->isEmpty() ? [Permission::read(Role::user($user->getId()))] : [],
|
||||
'functionId' => $function->getId(),
|
||||
'deploymentId' => $deployment->getId(),
|
||||
'trigger' => 'http', // http / schedule / event
|
||||
|
|
@ -1037,7 +1059,6 @@ App::post('/v1/functions/:functionId/executions')
|
|||
|
||||
/** Execute function */
|
||||
$executor = new Executor(App::getEnv('_APP_EXECUTOR_HOST'));
|
||||
$executionResponse = [];
|
||||
try {
|
||||
$executionResponse = $executor->createExecution(
|
||||
projectId: $project->getId(),
|
||||
|
|
@ -1059,12 +1080,12 @@ App::post('/v1/functions/:functionId/executions')
|
|||
$execution->setAttribute('stderr', $executionResponse['stderr']);
|
||||
$execution->setAttribute('time', $executionResponse['time']);
|
||||
} catch (\Throwable $th) {
|
||||
$endtime = \microtime(true);
|
||||
$time = $endtime - $execution->getCreatedAt();
|
||||
$execution->setAttribute('time', $time);
|
||||
$execution->setAttribute('status', 'failed');
|
||||
$execution->setAttribute('statusCode', $th->getCode());
|
||||
$execution->setAttribute('stderr', $th->getMessage());
|
||||
$interval = (new \DateTime())->diff(new \DateTime($execution->getCreatedAt()));
|
||||
$execution
|
||||
->setAttribute('time', (float)$interval->format('%s.%f'))
|
||||
->setAttribute('status', 'failed')
|
||||
->setAttribute('statusCode', $th->getCode())
|
||||
->setAttribute('stderr', $th->getMessage());
|
||||
Console::error($th->getMessage());
|
||||
}
|
||||
|
||||
|
|
@ -1117,24 +1138,30 @@ App::get('/v1/functions/:functionId/executions')
|
|||
throw new Exception(Exception::FUNCTION_NOT_FOUND);
|
||||
}
|
||||
|
||||
if (!empty($cursor)) {
|
||||
$cursorExecution = $dbForProject->getDocument('executions', $cursor);
|
||||
|
||||
if ($cursorExecution->isEmpty()) {
|
||||
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Execution '{$cursor}' for the 'cursor' value not found.");
|
||||
}
|
||||
}
|
||||
|
||||
$queries = [
|
||||
new Query('functionId', Query::TYPE_EQUAL, [$function->getId()])
|
||||
$filterQueries = [
|
||||
Query::equal('functionId', [$function->getId()])
|
||||
];
|
||||
|
||||
if (!empty($search)) {
|
||||
$queries[] = new Query('search', Query::TYPE_SEARCH, [$search]);
|
||||
$filterQueries[] = Query::search('search', $search);
|
||||
}
|
||||
|
||||
$results = $dbForProject->find('executions', $queries, $limit, $offset, [], [Database::ORDER_DESC], $cursorExecution ?? null, $cursorDirection);
|
||||
$total = $dbForProject->count('executions', $queries, APP_LIMIT_COUNT);
|
||||
$queries = [];
|
||||
$queries[] = Query::limit($limit);
|
||||
$queries[] = Query::offset($offset);
|
||||
$queries[] = Query::orderDesc('');
|
||||
if (!empty($cursor)) {
|
||||
$cursorDocument = $dbForProject->getDocument('executions', $cursor);
|
||||
|
||||
if ($cursorDocument->isEmpty()) {
|
||||
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Tag '{$cursor}' for the 'cursor' value not found.");
|
||||
}
|
||||
|
||||
$queries[] = $cursorDirection === Database::CURSOR_AFTER ? Query::cursorAfter($cursorDocument) : Query::cursorBefore($cursorDocument);
|
||||
}
|
||||
|
||||
$results = $dbForProject->find('executions', \array_merge($filterQueries, $queries));
|
||||
$total = $dbForProject->count('executions', $filterQueries, APP_LIMIT_COUNT);
|
||||
|
||||
$roles = Authorization::getRoles();
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
|
||||
|
|
|
|||
|
|
@ -17,8 +17,13 @@ use Utopia\Audit\Audit;
|
|||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\ID;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Permission;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Role;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\DatetimeValidator;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Domains\Domain;
|
||||
use Utopia\Registry\Registry;
|
||||
|
|
@ -26,7 +31,6 @@ use Appwrite\Extend\Exception;
|
|||
use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Hostname;
|
||||
use Utopia\Validator\Integer;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
|
@ -79,7 +83,7 @@ App::post('/v1/projects')
|
|||
$auths[$method['key'] ?? ''] = true;
|
||||
}
|
||||
|
||||
$projectId = ($projectId == 'unique()') ? $dbForConsole->getId() : $projectId;
|
||||
$projectId = ($projectId == 'unique()') ? ID::unique() : $projectId;
|
||||
|
||||
if ($projectId === 'console') {
|
||||
throw new Exception(Exception::PROJECT_RESERVED_PROJECT, "'console' is a reserved project.");
|
||||
|
|
@ -87,8 +91,13 @@ App::post('/v1/projects')
|
|||
|
||||
$project = $dbForConsole->createDocument('projects', new Document([
|
||||
'$id' => $projectId,
|
||||
'$read' => ['team:' . $teamId],
|
||||
'$write' => ['team:' . $teamId . '/owner', 'team:' . $teamId . '/developer'],
|
||||
'$permissions' => [
|
||||
Permission::read(Role::team(ID::custom($teamId))),
|
||||
Permission::update(Role::team(ID::custom($teamId), 'owner')),
|
||||
Permission::update(Role::team(ID::custom($teamId), 'developer')),
|
||||
Permission::delete(Role::team(ID::custom($teamId), 'owner')),
|
||||
Permission::delete(Role::team(ID::custom($teamId), 'developer')),
|
||||
],
|
||||
'name' => $name,
|
||||
'teamInternalId' => $team->getInternalId(),
|
||||
'teamId' => $team->getId(),
|
||||
|
|
@ -101,7 +110,7 @@ App::post('/v1/projects')
|
|||
'legalState' => $legalState,
|
||||
'legalCity' => $legalCity,
|
||||
'legalAddress' => $legalAddress,
|
||||
'legalTaxId' => $legalTaxId,
|
||||
'legalTaxId' => ID::custom($legalTaxId),
|
||||
'services' => new stdClass(),
|
||||
'platforms' => null,
|
||||
'authProviders' => [],
|
||||
|
|
@ -176,27 +185,33 @@ App::get('/v1/projects')
|
|||
->param('offset', 0, new Range(0, APP_LIMIT_COUNT), 'Results offset. The default value is 0. Use this param to manage pagination. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
|
||||
->param('cursor', '', new UID(), 'ID of the project used as the starting point for the query, excluding the project itself. Should be used for efficient pagination when working with large sets of data. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
|
||||
->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor, can be either \'before\' or \'after\'.', true)
|
||||
->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true)
|
||||
->param('orderType', Database::ORDER_ASC, new WhiteList([Database::ORDER_ASC, Database::ORDER_DESC], true), 'Order result by ' . Database::ORDER_ASC . ' or ' . Database::ORDER_DESC . ' order.', true)
|
||||
->inject('response')
|
||||
->inject('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);
|
||||
$filterQueries = [];
|
||||
|
||||
if ($cursorProject->isEmpty()) {
|
||||
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Project '{$cursor}' for the 'cursor' value not found.");
|
||||
}
|
||||
if (!empty($search)) {
|
||||
$filterQueries[] = Query::search('search', $search);
|
||||
}
|
||||
|
||||
$queries = [];
|
||||
$queries[] = Query::limit($limit);
|
||||
$queries[] = Query::offset($offset);
|
||||
$queries[] = $orderType === Database::ORDER_ASC ? Query::orderAsc('') : Query::orderDesc('');
|
||||
if (!empty($cursor)) {
|
||||
$cursorDocument = $dbForConsole->getDocument('projects', $cursor);
|
||||
|
||||
if (!empty($search)) {
|
||||
$queries[] = new Query('search', Query::TYPE_SEARCH, [$search]);
|
||||
if ($cursorDocument->isEmpty()) {
|
||||
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Project '{$cursor}' for the 'cursor' value not found.");
|
||||
}
|
||||
|
||||
$queries[] = $cursorDirection === Database::CURSOR_AFTER ? Query::cursorAfter($cursorDocument) : Query::cursorBefore($cursorDocument);
|
||||
}
|
||||
|
||||
$results = $dbForConsole->find('projects', $queries, $limit, $offset, [], [$orderType], $cursorProject ?? null, $cursorDirection);
|
||||
$total = $dbForConsole->count('projects', $queries, APP_LIMIT_COUNT);
|
||||
$results = $dbForConsole->find('projects', \array_merge($filterQueries, $queries));
|
||||
$total = $dbForConsole->count('projects', $filterQueries, APP_LIMIT_COUNT);
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'projects' => $results,
|
||||
|
|
@ -293,9 +308,11 @@ App::get('/v1/projects/:projectId/usage')
|
|||
$period = $periods[$range]['period'];
|
||||
|
||||
$requestDocs = $dbForProject->find('stats', [
|
||||
new Query('period', Query::TYPE_EQUAL, [$period]),
|
||||
new Query('metric', Query::TYPE_EQUAL, [$metric]),
|
||||
], $limit, 0, ['time'], [Database::ORDER_DESC]);
|
||||
Query::equal('period', [$period]),
|
||||
Query::equal('metric', [$metric]),
|
||||
Query::limit($limit),
|
||||
Query::orderDesc('time'),
|
||||
]);
|
||||
|
||||
$stats[$metric] = [];
|
||||
foreach ($requestDocs as $requestDoc) {
|
||||
|
|
@ -315,7 +332,7 @@ App::get('/v1/projects/:projectId/usage')
|
|||
};
|
||||
$stats[$metric][] = [
|
||||
'value' => 0,
|
||||
'date' => ($stats[$metric][$last]['date'] ?? \time()) - $diff, // time of last metric minus period
|
||||
'date' => DateTime::addSeconds(new \DateTime($stats[$metric][$last]['date'] ?? null), -1 * $diff),
|
||||
];
|
||||
$backfill--;
|
||||
}
|
||||
|
|
@ -589,9 +606,12 @@ App::post('/v1/projects/:projectId/webhooks')
|
|||
$security = (bool) filter_var($security, FILTER_VALIDATE_BOOLEAN);
|
||||
|
||||
$webhook = new Document([
|
||||
'$id' => $dbForConsole->getId(),
|
||||
'$read' => ['role:all'],
|
||||
'$write' => ['role:all'],
|
||||
'$id' => ID::unique(),
|
||||
'$permissions' => [
|
||||
Permission::read(Role::any()),
|
||||
Permission::update(Role::any()),
|
||||
Permission::delete(Role::any()),
|
||||
],
|
||||
'projectInternalId' => $project->getInternalId(),
|
||||
'projectId' => $project->getId(),
|
||||
'name' => $name,
|
||||
|
|
@ -633,8 +653,9 @@ App::get('/v1/projects/:projectId/webhooks')
|
|||
}
|
||||
|
||||
$webhooks = $dbForConsole->find('webhooks', [
|
||||
new Query('projectInternalId', Query::TYPE_EQUAL, [$project->getInternalId()])
|
||||
], 5000);
|
||||
Query::equal('projectInternalId', [$project->getInternalId()]),
|
||||
Query::limit(5000),
|
||||
]);
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'webhooks' => $webhooks,
|
||||
|
|
@ -665,8 +686,8 @@ App::get('/v1/projects/:projectId/webhooks/:webhookId')
|
|||
}
|
||||
|
||||
$webhook = $dbForConsole->findOne('webhooks', [
|
||||
new Query('_uid', Query::TYPE_EQUAL, [$webhookId]),
|
||||
new Query('projectInternalId', Query::TYPE_EQUAL, [$project->getInternalId()])
|
||||
Query::equal('_uid', [$webhookId]),
|
||||
Query::equal('projectInternalId', [$project->getInternalId()]),
|
||||
]);
|
||||
|
||||
if ($webhook === false || $webhook->isEmpty()) {
|
||||
|
|
@ -707,8 +728,8 @@ App::put('/v1/projects/:projectId/webhooks/:webhookId')
|
|||
$security = ($security === '1' || $security === 'true' || $security === 1 || $security === true);
|
||||
|
||||
$webhook = $dbForConsole->findOne('webhooks', [
|
||||
new Query('_uid', Query::TYPE_EQUAL, [$webhookId]),
|
||||
new Query('projectInternalId', Query::TYPE_EQUAL, [$project->getInternalId()])
|
||||
Query::equal('_uid', [$webhookId]),
|
||||
Query::equal('projectInternalId', [$project->getInternalId()]),
|
||||
]);
|
||||
|
||||
if ($webhook === false || $webhook->isEmpty()) {
|
||||
|
|
@ -753,8 +774,8 @@ App::patch('/v1/projects/:projectId/webhooks/:webhookId/signature')
|
|||
}
|
||||
|
||||
$webhook = $dbForConsole->findOne('webhooks', [
|
||||
new Query('_uid', Query::TYPE_EQUAL, [$webhookId]),
|
||||
new Query('projectInternalId', Query::TYPE_EQUAL, [$project->getInternalId()])
|
||||
Query::equal('_uid', [$webhookId]),
|
||||
Query::equal('projectInternalId', [$project->getInternalId()]),
|
||||
]);
|
||||
|
||||
if ($webhook === false || $webhook->isEmpty()) {
|
||||
|
|
@ -791,8 +812,8 @@ App::delete('/v1/projects/:projectId/webhooks/:webhookId')
|
|||
}
|
||||
|
||||
$webhook = $dbForConsole->findOne('webhooks', [
|
||||
new Query('_uid', Query::TYPE_EQUAL, [$webhookId]),
|
||||
new Query('projectInternalId', Query::TYPE_EQUAL, [$project->getInternalId()])
|
||||
Query::equal('_uid', [$webhookId]),
|
||||
Query::equal('projectInternalId', [$project->getInternalId()]),
|
||||
]);
|
||||
|
||||
if ($webhook === false || $webhook->isEmpty()) {
|
||||
|
|
@ -821,10 +842,10 @@ App::post('/v1/projects/:projectId/keys')
|
|||
->param('projectId', null, new UID(), 'Project unique ID.')
|
||||
->param('name', null, new Text(128), 'Key name. Max length: 128 chars.')
|
||||
->param('scopes', null, new ArrayList(new WhiteList(array_keys(Config::getParam('scopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Key scopes list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed.')
|
||||
->param('expire', 0, new Integer(), 'Key expiration time in Unix timestamp. Use 0 for unlimited expiration.', true)
|
||||
->param('expire', null, new DatetimeValidator(), 'Expiration time in DateTime. Use null for unlimited expiration.', true)
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, string $name, array $scopes, int $expire, Response $response, Database $dbForConsole) {
|
||||
->action(function (string $projectId, string $name, array $scopes, ?string $expire, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
|
|
@ -833,9 +854,12 @@ App::post('/v1/projects/:projectId/keys')
|
|||
}
|
||||
|
||||
$key = new Document([
|
||||
'$id' => $dbForConsole->getId(),
|
||||
'$read' => ['role:all'],
|
||||
'$write' => ['role:all'],
|
||||
'$id' => ID::unique(),
|
||||
'$permissions' => [
|
||||
Permission::read(Role::any()),
|
||||
Permission::update(Role::any()),
|
||||
Permission::delete(Role::any()),
|
||||
],
|
||||
'projectInternalId' => $project->getInternalId(),
|
||||
'projectId' => $project->getId(),
|
||||
'name' => $name,
|
||||
|
|
@ -874,8 +898,9 @@ App::get('/v1/projects/:projectId/keys')
|
|||
}
|
||||
|
||||
$keys = $dbForConsole->find('keys', [
|
||||
new Query('projectInternalId', Query::TYPE_EQUAL, [$project->getInternalId()]),
|
||||
], 5000);
|
||||
Query::equal('projectInternalId', [$project->getInternalId()]),
|
||||
Query::limit(5000),
|
||||
]);
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'keys' => $keys,
|
||||
|
|
@ -906,8 +931,8 @@ App::get('/v1/projects/:projectId/keys/:keyId')
|
|||
}
|
||||
|
||||
$key = $dbForConsole->findOne('keys', [
|
||||
new Query('_uid', Query::TYPE_EQUAL, [$keyId]),
|
||||
new Query('projectInternalId', Query::TYPE_EQUAL, [$project->getInternalId()])
|
||||
Query::equal('_uid', [$keyId]),
|
||||
Query::equal('projectInternalId', [$project->getInternalId()]),
|
||||
]);
|
||||
|
||||
if ($key === false || $key->isEmpty()) {
|
||||
|
|
@ -931,10 +956,10 @@ App::put('/v1/projects/:projectId/keys/:keyId')
|
|||
->param('keyId', null, new UID(), 'Key unique ID.')
|
||||
->param('name', null, new Text(128), 'Key name. Max length: 128 chars.')
|
||||
->param('scopes', null, new ArrayList(new WhiteList(array_keys(Config::getParam('scopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Key scopes list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' events are allowed.')
|
||||
->param('expire', 0, new Integer(), 'Key expiration time in Unix timestamp. Use 0 for unlimited expiration.', true)
|
||||
->param('expire', null, new DatetimeValidator(), 'Expiration time in DateTime. Use null for unlimited expiration.', true)
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, string $keyId, string $name, array $scopes, int $expire, Response $response, Database $dbForConsole) {
|
||||
->action(function (string $projectId, string $keyId, string $name, array $scopes, ?string $expire, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
|
|
@ -943,8 +968,8 @@ App::put('/v1/projects/:projectId/keys/:keyId')
|
|||
}
|
||||
|
||||
$key = $dbForConsole->findOne('keys', [
|
||||
new Query('_uid', Query::TYPE_EQUAL, [$keyId]),
|
||||
new Query('projectInternalId', Query::TYPE_EQUAL, [$project->getInternalId()])
|
||||
Query::equal('_uid', [$keyId]),
|
||||
Query::equal('projectInternalId', [$project->getInternalId()]),
|
||||
]);
|
||||
|
||||
if ($key === false || $key->isEmpty()) {
|
||||
|
|
@ -986,8 +1011,8 @@ App::delete('/v1/projects/:projectId/keys/:keyId')
|
|||
}
|
||||
|
||||
$key = $dbForConsole->findOne('keys', [
|
||||
new Query('_uid', Query::TYPE_EQUAL, [$keyId]),
|
||||
new Query('projectInternalId', Query::TYPE_EQUAL, [$project->getInternalId()])
|
||||
Query::equal('_uid', [$keyId]),
|
||||
Query::equal('projectInternalId', [$project->getInternalId()]),
|
||||
]);
|
||||
|
||||
if ($key === false || $key->isEmpty()) {
|
||||
|
|
@ -1029,9 +1054,12 @@ App::post('/v1/projects/:projectId/platforms')
|
|||
}
|
||||
|
||||
$platform = new Document([
|
||||
'$id' => $dbForConsole->getId(),
|
||||
'$read' => ['role:all'],
|
||||
'$write' => ['role:all'],
|
||||
'$id' => ID::unique(),
|
||||
'$permissions' => [
|
||||
Permission::read(Role::any()),
|
||||
Permission::update(Role::any()),
|
||||
Permission::delete(Role::any()),
|
||||
],
|
||||
'projectInternalId' => $project->getInternalId(),
|
||||
'projectId' => $project->getId(),
|
||||
'type' => $type,
|
||||
|
|
@ -1071,8 +1099,9 @@ App::get('/v1/projects/:projectId/platforms')
|
|||
}
|
||||
|
||||
$platforms = $dbForConsole->find('platforms', [
|
||||
new Query('projectId', Query::TYPE_EQUAL, [$project->getId()])
|
||||
], 5000);
|
||||
Query::equal('projectId', [$project->getId()]),
|
||||
Query::limit(5000),
|
||||
]);
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'platforms' => $platforms,
|
||||
|
|
@ -1103,8 +1132,8 @@ App::get('/v1/projects/:projectId/platforms/:platformId')
|
|||
}
|
||||
|
||||
$platform = $dbForConsole->findOne('platforms', [
|
||||
new Query('_uid', Query::TYPE_EQUAL, [$platformId]),
|
||||
new Query('projectInternalId', Query::TYPE_EQUAL, [$project->getInternalId()])
|
||||
Query::equal('_uid', [$platformId]),
|
||||
Query::equal('projectInternalId', [$project->getInternalId()]),
|
||||
]);
|
||||
|
||||
if ($platform === false || $platform->isEmpty()) {
|
||||
|
|
@ -1140,8 +1169,8 @@ App::put('/v1/projects/:projectId/platforms/:platformId')
|
|||
}
|
||||
|
||||
$platform = $dbForConsole->findOne('platforms', [
|
||||
new Query('_uid', Query::TYPE_EQUAL, [$platformId]),
|
||||
new Query('projectInternalId', Query::TYPE_EQUAL, [$project->getInternalId()])
|
||||
Query::equal('_uid', [$platformId]),
|
||||
Query::equal('projectInternalId', [$project->getInternalId()]),
|
||||
]);
|
||||
|
||||
if ($platform === false || $platform->isEmpty()) {
|
||||
|
|
@ -1184,8 +1213,8 @@ App::delete('/v1/projects/:projectId/platforms/:platformId')
|
|||
}
|
||||
|
||||
$platform = $dbForConsole->findOne('platforms', [
|
||||
new Query('_uid', Query::TYPE_EQUAL, [$platformId]),
|
||||
new Query('projectInternalId', Query::TYPE_EQUAL, [$project->getInternalId()])
|
||||
Query::equal('_uid', [$platformId]),
|
||||
Query::equal('projectInternalId', [$project->getInternalId()]),
|
||||
]);
|
||||
|
||||
if ($platform === false || $platform->isEmpty()) {
|
||||
|
|
@ -1224,8 +1253,8 @@ App::post('/v1/projects/:projectId/domains')
|
|||
}
|
||||
|
||||
$document = $dbForConsole->findOne('domains', [
|
||||
new Query('domain', Query::TYPE_EQUAL, [$domain]),
|
||||
new Query('projectInternalId', Query::TYPE_EQUAL, [$project->getInternalId()]),
|
||||
Query::equal('domain', [$domain]),
|
||||
Query::equal('projectInternalId', [$project->getInternalId()]),
|
||||
]);
|
||||
|
||||
if ($document && !$document->isEmpty()) {
|
||||
|
|
@ -1241,12 +1270,15 @@ App::post('/v1/projects/:projectId/domains')
|
|||
$domain = new Domain($domain);
|
||||
|
||||
$domain = new Document([
|
||||
'$id' => $dbForConsole->getId(),
|
||||
'$read' => ['role:all'],
|
||||
'$write' => ['role:all'],
|
||||
'$id' => ID::unique(),
|
||||
'$permissions' => [
|
||||
Permission::read(Role::any()),
|
||||
Permission::update(Role::any()),
|
||||
Permission::delete(Role::any()),
|
||||
],
|
||||
'projectInternalId' => $project->getInternalId(),
|
||||
'projectId' => $project->getId(),
|
||||
'updated' => \time(),
|
||||
'updated' => DateTime::now(),
|
||||
'domain' => $domain->get(),
|
||||
'tld' => $domain->getSuffix(),
|
||||
'registerable' => $domain->getRegisterable(),
|
||||
|
|
@ -1284,8 +1316,9 @@ App::get('/v1/projects/:projectId/domains')
|
|||
}
|
||||
|
||||
$domains = $dbForConsole->find('domains', [
|
||||
new Query('projectInternalId', Query::TYPE_EQUAL, [$project->getInternalId()])
|
||||
], 5000);
|
||||
Query::equal('projectInternalId', [$project->getInternalId()]),
|
||||
Query::limit(5000),
|
||||
]);
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'domains' => $domains,
|
||||
|
|
@ -1316,8 +1349,8 @@ App::get('/v1/projects/:projectId/domains/:domainId')
|
|||
}
|
||||
|
||||
$domain = $dbForConsole->findOne('domains', [
|
||||
new Query('_uid', Query::TYPE_EQUAL, [$domainId]),
|
||||
new Query('projectInternalId', Query::TYPE_EQUAL, [$project->getInternalId()])
|
||||
Query::equal('_uid', [$domainId]),
|
||||
Query::equal('projectInternalId', [$project->getInternalId()]),
|
||||
]);
|
||||
|
||||
if ($domain === false || $domain->isEmpty()) {
|
||||
|
|
@ -1350,8 +1383,8 @@ App::patch('/v1/projects/:projectId/domains/:domainId/verification')
|
|||
}
|
||||
|
||||
$domain = $dbForConsole->findOne('domains', [
|
||||
new Query('_uid', Query::TYPE_EQUAL, [$domainId]),
|
||||
new Query('projectInternalId', Query::TYPE_EQUAL, [$project->getInternalId()])
|
||||
Query::equal('_uid', [$domainId]),
|
||||
Query::equal('projectInternalId', [$project->getInternalId()]),
|
||||
]);
|
||||
|
||||
if ($domain === false || $domain->isEmpty()) {
|
||||
|
|
@ -1410,8 +1443,8 @@ App::delete('/v1/projects/:projectId/domains/:domainId')
|
|||
}
|
||||
|
||||
$domain = $dbForConsole->findOne('domains', [
|
||||
new Query('_uid', Query::TYPE_EQUAL, [$domainId]),
|
||||
new Query('projectInternalId', Query::TYPE_EQUAL, [$project->getInternalId()])
|
||||
Query::equal('_uid', [$domainId]),
|
||||
Query::equal('projectInternalId', [$project->getInternalId()]),
|
||||
]);
|
||||
|
||||
if ($domain === false || $domain->isEmpty()) {
|
||||
|
|
|
|||
|
|
@ -12,10 +12,15 @@ use Utopia\App;
|
|||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Exception\Duplicate;
|
||||
use Utopia\Database\Exception\Authorization as AuthorizationException;
|
||||
use Utopia\Database\Exception\Duplicate as DuplicateException;
|
||||
use Utopia\Database\Exception\Structure as StructureException;
|
||||
use Utopia\Database\ID;
|
||||
use Utopia\Database\Permission;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Role;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Permissions;
|
||||
use Utopia\Database\Validator\UID;
|
||||
|
|
@ -54,9 +59,8 @@ App::post('/v1/storage/buckets')
|
|||
->label('sdk.response.model', Response::MODEL_BUCKET)
|
||||
->param('bucketId', '', 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), 'Bucket name')
|
||||
->param('permission', null, new WhiteList(['file', 'bucket']), 'Permissions type model to use for reading files in this bucket. You can use bucket-level permission set once on the bucket using the `read` and `write` params, or you can set file-level permission where each file read and write params will decide who has access to read and write to each file individually. [learn more about permissions](/docs/permissions) and get a full list of available permissions.')
|
||||
->param('read', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.', true)
|
||||
->param('write', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.', true)
|
||||
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of permission strings. By default no user is granted with any permissions. [Learn more about permissions](/docs/permissions).', true)
|
||||
->param('fileSecurity', false, new Boolean(true), 'Enables configuring permissions for individual file. A user needs one of file or bucket level permissions to access a file. [Learn more about permissions](/docs/permissions).')
|
||||
->param('enabled', true, new Boolean(true), 'Is bucket enabled?', true)
|
||||
->param('maximumFileSize', (int) App::getEnv('_APP_STORAGE_LIMIT', 0), new Range(1, (int) App::getEnv('_APP_STORAGE_LIMIT', 0)), 'Maximum file size allowed in bytes. Maximum allowed value is ' . Storage::human(App::getEnv('_APP_STORAGE_LIMIT', 0), 0) . '. For self-hosted setups you can change the max limit by changing the `_APP_STORAGE_LIMIT` environment variable. [Learn more about storage environment variables](docs/environment-variables#storage)', true)
|
||||
->param('allowedFileExtensions', [], new ArrayList(new Text(64), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Allowed file extensions. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' extensions are allowed, each 64 characters long.', true)
|
||||
|
|
@ -65,9 +69,13 @@ App::post('/v1/storage/buckets')
|
|||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
->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 $events) {
|
||||
->action(function (string $bucketId, string $name, ?array $permissions, bool $fileSecurity, bool $enabled, int $maximumFileSize, array $allowedFileExtensions, bool $encryption, bool $antivirus, Response $response, Database $dbForProject, Event $events) {
|
||||
|
||||
$bucketId = $bucketId === 'unique()' ? ID::unique() : $bucketId;
|
||||
|
||||
// Map aggregate permissions into the multiple permissions they represent.
|
||||
$permissions = Permission::aggregate($permissions);
|
||||
|
||||
$bucketId = $bucketId === 'unique()' ? $dbForProject->getId() : $bucketId;
|
||||
try {
|
||||
$files = Config::getParam('collections', [])['files'] ?? [];
|
||||
if (empty($files)) {
|
||||
|
|
@ -101,25 +109,24 @@ App::post('/v1/storage/buckets')
|
|||
]);
|
||||
}
|
||||
|
||||
$bucket = $dbForProject->createDocument('buckets', new Document([
|
||||
$dbForProject->createDocument('buckets', new Document([
|
||||
'$id' => $bucketId,
|
||||
'$collection' => 'buckets',
|
||||
'$permissions' => $permissions,
|
||||
'name' => $name,
|
||||
'permission' => $permission,
|
||||
'maximumFileSize' => $maximumFileSize,
|
||||
'allowedFileExtensions' => $allowedFileExtensions,
|
||||
'enabled' => (bool) filter_var($enabled, FILTER_VALIDATE_BOOLEAN),
|
||||
'encryption' => (bool) filter_var($encryption, FILTER_VALIDATE_BOOLEAN),
|
||||
'antivirus' => (bool) filter_var($antivirus, FILTER_VALIDATE_BOOLEAN),
|
||||
'$read' => $read ?? [],
|
||||
'$write' => $write ?? [],
|
||||
'fileSecurity' => $fileSecurity,
|
||||
'enabled' => $enabled,
|
||||
'encryption' => $encryption,
|
||||
'antivirus' => $antivirus,
|
||||
'search' => implode(' ', [$bucketId, $name]),
|
||||
]));
|
||||
|
||||
$bucket = $dbForProject->getDocument('buckets', $bucketId);
|
||||
|
||||
$dbForProject->createCollection('bucket_' . $bucket->getInternalId(), $attributes, $indexes);
|
||||
} catch (Duplicate $th) {
|
||||
} catch (Duplicate) {
|
||||
throw new Exception(Exception::STORAGE_BUCKET_ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
|
|
@ -148,24 +155,34 @@ App::get('/v1/storage/buckets')
|
|||
->param('offset', 0, new Range(0, APP_LIMIT_COUNT), 'Results offset. The default value is 0. Use this param to manage pagination.', true)
|
||||
->param('cursor', '', new UID(), 'ID of the bucket used as the starting point for the query, excluding the bucket itself. Should be used for efficient pagination when working with large sets of data.', true)
|
||||
->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor, can be either \'before\' or \'after\'.', true)
|
||||
->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true)
|
||||
->param('orderType', Database::ORDER_ASC, new WhiteList([Database::ORDER_ASC, Database::ORDER_DESC], true), 'Order result by ' . Database::ORDER_ASC . ' or ' . Database::ORDER_DESC . ' order.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->action(function (string $search, int $limit, int $offset, string $cursor, string $cursorDirection, string $orderType, Response $response, Database $dbForProject) {
|
||||
|
||||
$queries = ($search) ? [new Query('name', Query::TYPE_SEARCH, [$search])] : [];
|
||||
$filterQueries = [];
|
||||
|
||||
if (!empty($search)) {
|
||||
$filterQueries[] = Query::search('name', $search);
|
||||
}
|
||||
|
||||
$queries = [];
|
||||
$queries[] = Query::limit($limit);
|
||||
$queries[] = Query::offset($offset);
|
||||
$queries[] = $orderType === Database::ORDER_ASC ? Query::orderAsc('') : Query::orderDesc('');
|
||||
if (!empty($cursor)) {
|
||||
$cursorBucket = $dbForProject->getDocument('buckets', $cursor);
|
||||
$cursorDocument = $dbForProject->getDocument('buckets', $cursor);
|
||||
|
||||
if ($cursorBucket->isEmpty()) {
|
||||
if ($cursorDocument->isEmpty()) {
|
||||
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Bucket '{$cursor}' for the 'cursor' value not found.");
|
||||
}
|
||||
|
||||
$queries[] = $cursorDirection === Database::CURSOR_AFTER ? Query::cursorAfter($cursorDocument) : Query::cursorBefore($cursorDocument);
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'buckets' => $dbForProject->find('buckets', $queries, $limit, $offset, [], [$orderType], $cursorBucket ?? null, $cursorDirection),
|
||||
'total' => $dbForProject->count('buckets', $queries, APP_LIMIT_COUNT),
|
||||
'buckets' => $dbForProject->find('buckets', \array_merge($filterQueries, $queries)),
|
||||
'total' => $dbForProject->count('buckets', $filterQueries, APP_LIMIT_COUNT),
|
||||
]), Response::MODEL_BUCKET_LIST);
|
||||
});
|
||||
|
||||
|
|
@ -211,9 +228,8 @@ App::put('/v1/storage/buckets/:bucketId')
|
|||
->label('sdk.response.model', Response::MODEL_BUCKET)
|
||||
->param('bucketId', '', new UID(), 'Bucket unique ID.')
|
||||
->param('name', null, new Text(128), 'Bucket name', false)
|
||||
->param('permission', null, new WhiteList(['file', 'bucket']), 'Permissions type model to use for reading files in this bucket. You can use bucket-level permission set once on the bucket using the `read` and `write` params, or you can set file-level permission where each file read and write params will decide who has access to read and write to each file individually. [learn more about permissions](/docs/permissions) and get a full list of available permissions.')
|
||||
->param('read', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of strings with read permissions. By default inherits the existing read permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.', true)
|
||||
->param('write', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of strings with write permissions. By default inherits the existing write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.', true)
|
||||
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of permission strings. By default the current permissions are inherited. [Learn more about permissions](/docs/permissions).', true)
|
||||
->param('fileSecurity', false, new Boolean(true), 'Enables configuring permissions for individual file. A user needs one of file or bucket level permissions to access a file. [Learn more about permissions](/docs/permissions).')
|
||||
->param('enabled', true, new Boolean(true), 'Is bucket enabled?', true)
|
||||
->param('maximumFileSize', null, new Range(1, (int) App::getEnv('_APP_STORAGE_LIMIT', 0)), 'Maximum file size allowed in bytes. Maximum allowed value is ' . Storage::human((int)App::getEnv('_APP_STORAGE_LIMIT', 0), 0) . '. For self hosted version you can change the limit by changing _APP_STORAGE_LIMIT environment variable. [Learn more about storage environment variables](docs/environment-variables#storage)', true)
|
||||
->param('allowedFileExtensions', [], new ArrayList(new Text(64), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Allowed file extensions. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' extensions are allowed, each 64 characters long.', true)
|
||||
|
|
@ -222,31 +238,36 @@ App::put('/v1/storage/buckets/:bucketId')
|
|||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
->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 $events) {
|
||||
->action(function (string $bucketId, string $name, ?array $permissions, bool $fileSecurity, bool $enabled, ?int $maximumFileSize, array $allowedFileExtensions, bool $encryption, bool $antivirus, Response $response, Database $dbForProject, Event $events) {
|
||||
$bucket = $dbForProject->getDocument('buckets', $bucketId);
|
||||
|
||||
if ($bucket->isEmpty()) {
|
||||
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
|
||||
}
|
||||
|
||||
$read ??= $bucket->getAttribute('$read', []); // By default inherit read permissions
|
||||
$write ??= $bucket->getAttribute('$write', []); // By default inherit write permissions
|
||||
$permissions ??= $bucket->getPermissions();
|
||||
$maximumFileSize ??= $bucket->getAttribute('maximumFileSize', (int) App::getEnv('_APP_STORAGE_LIMIT', 0));
|
||||
$allowedFileExtensions ??= $bucket->getAttribute('allowedFileExtensions', []);
|
||||
$enabled ??= $bucket->getAttribute('enabled', true);
|
||||
$encryption ??= $bucket->getAttribute('encryption', true);
|
||||
$antivirus ??= $bucket->getAttribute('antivirus', true);
|
||||
|
||||
/**
|
||||
* Map aggregate permissions into the multiple permissions they represent,
|
||||
* accounting for the resource type given that some types not allowed specific permissions.
|
||||
*/
|
||||
// Map aggregate permissions into the multiple permissions they represent.
|
||||
$permissions = Permission::aggregate($permissions);
|
||||
|
||||
$bucket = $dbForProject->updateDocument('buckets', $bucket->getId(), $bucket
|
||||
->setAttribute('name', $name)
|
||||
->setAttribute('$read', $read)
|
||||
->setAttribute('$write', $write)
|
||||
->setAttribute('$permissions', $permissions)
|
||||
->setAttribute('maximumFileSize', $maximumFileSize)
|
||||
->setAttribute('allowedFileExtensions', $allowedFileExtensions)
|
||||
->setAttribute('enabled', (bool) filter_var($enabled, FILTER_VALIDATE_BOOLEAN))
|
||||
->setAttribute('encryption', (bool) filter_var($encryption, FILTER_VALIDATE_BOOLEAN))
|
||||
->setAttribute('permission', $permission)
|
||||
->setAttribute('antivirus', (bool) filter_var($antivirus, FILTER_VALIDATE_BOOLEAN)));
|
||||
->setAttribute('fileSecurity', $fileSecurity)
|
||||
->setAttribute('enabled', $enabled)
|
||||
->setAttribute('encryption', $encryption)
|
||||
->setAttribute('antivirus', $antivirus));
|
||||
|
||||
$events
|
||||
->setParam('bucketId', $bucket->getId())
|
||||
|
|
@ -305,6 +326,8 @@ App::post('/v1/storage/buckets/:bucketId/files')
|
|||
->label('audits.resource', 'files/{response.$id}')
|
||||
->label('usage.metric', 'files.{scope}.requests.create')
|
||||
->label('usage.params', ['bucketId:{request.bucketId}'])
|
||||
->label('abuse-limit', 60)
|
||||
->label('abuse-time', 60)
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'storage')
|
||||
->label('sdk.method', 'createFile')
|
||||
|
|
@ -317,8 +340,7 @@ App::post('/v1/storage/buckets/:bucketId/files')
|
|||
->param('bucketId', null, new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).')
|
||||
->param('fileId', '', new CustomId(), 'File ID. Choose your own unique ID or pass the string "unique()" to auto generate it. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('file', [], new File(), 'Binary file.', false)
|
||||
->param('read', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of strings with read permissions. By default only the current user is granted with read permissions. [learn more about permissions](https://appwrite.io/docs/permissions) and get a full list of available permissions.', true)
|
||||
->param('write', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of strings with write permissions. By default only the current user is granted with write permissions. [learn more about permissions](https://appwrite.io/docs/permissions) and get a full list of available permissions.', true)
|
||||
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE]), 'An array of permission strings. By default the current user is granted with all permissions. [Learn more about permissions](/docs/permissions).', true)
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
|
|
@ -328,53 +350,59 @@ App::post('/v1/storage/buckets/:bucketId/files')
|
|||
->inject('deviceFiles')
|
||||
->inject('deviceLocal')
|
||||
->inject('deletes')
|
||||
->action(function (string $bucketId, string $fileId, mixed $file, ?array $read, ?array $write, Request $request, Response $response, Database $dbForProject, Document $user, Event $events, string $mode, Device $deviceFiles, Device $deviceLocal, Delete $deletes) {
|
||||
->action(function (string $bucketId, string $fileId, mixed $file, ?array $permissions, Request $request, Response $response, Database $dbForProject, Document $user, Event $events, string $mode, Device $deviceFiles, Device $deviceLocal, Delete $deletes) {
|
||||
|
||||
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
||||
|
||||
if (
|
||||
$bucket->isEmpty()
|
||||
|| (!$bucket->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)
|
||||
) {
|
||||
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)) {
|
||||
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
|
||||
}
|
||||
|
||||
// Check bucket permissions when enforced
|
||||
$permissionBucket = $bucket->getAttribute('permission') === 'bucket';
|
||||
if ($permissionBucket) {
|
||||
$validator = new Authorization('write');
|
||||
if (!$validator->isValid($bucket->getWrite())) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
$validator = new Authorization(Database::PERMISSION_CREATE);
|
||||
if (!$validator->isValid($bucket->getCreate())) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
$allowedPermissions = [
|
||||
Database::PERMISSION_READ,
|
||||
Database::PERMISSION_UPDATE,
|
||||
Database::PERMISSION_DELETE,
|
||||
];
|
||||
|
||||
// Map aggregate permissions to into the set of individual permissions they represent.
|
||||
$permissions = Permission::aggregate($permissions, $allowedPermissions);
|
||||
|
||||
// Add permissions for current the user if none were provided.
|
||||
if (\is_null($permissions)) {
|
||||
$permissions = [];
|
||||
if (!empty($user->getId())) {
|
||||
foreach ($allowedPermissions as $permission) {
|
||||
$permissions[] = (new Permission($permission, 'user', $user->getId()))->toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$read = (is_null($read) && !$user->isEmpty()) ? ['user:' . $user->getId()] : $read ?? []; // By default set read permissions for user
|
||||
$write = (is_null($write) && !$user->isEmpty()) ? ['user:' . $user->getId()] : $write ?? [];
|
||||
|
||||
// Users can only add their roles to files, API keys and Admin users can add any
|
||||
// Users can only manage their own roles, API keys and Admin users can manage any
|
||||
$roles = Authorization::getRoles();
|
||||
|
||||
if (!Auth::isAppUser($roles) && !Auth::isPrivilegedUser($roles)) {
|
||||
foreach ($read as $role) {
|
||||
if (!Authorization::isRole($role)) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED, 'Read permissions must be one of: (' . \implode(', ', $roles) . ')', 400);
|
||||
}
|
||||
}
|
||||
foreach ($write as $role) {
|
||||
if (!Authorization::isRole($role)) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED, 'Write permissions must be one of: (' . \implode(', ', $roles) . ')', 400);
|
||||
foreach (Database::PERMISSIONS as $type) {
|
||||
foreach ($permissions as $permission) {
|
||||
$permission = Permission::parse($permission);
|
||||
if ($permission->getPermission() != $type) {
|
||||
continue;
|
||||
}
|
||||
$role = (new Role(
|
||||
$permission->getRole(),
|
||||
$permission->getIdentifier(),
|
||||
$permission->getDimension()
|
||||
))->toString();
|
||||
if (!Authorization::isRole($role)) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED, 'Permissions must be one of: (' . \implode(', ', $roles) . ')');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$file = $request->getFiles('file');
|
||||
|
||||
/**
|
||||
* Validators
|
||||
*/
|
||||
$allowedFileExtensions = $bucket->getAttribute('allowedFileExtensions', []);
|
||||
$fileExt = new FileExt($allowedFileExtensions);
|
||||
|
||||
$maximumFileSize = $bucket->getAttribute('maximumFileSize', 0);
|
||||
if ($maximumFileSize > (int) App::getEnv('_APP_STORAGE_LIMIT', 0)) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Maximum bucket file size is larger than _APP_STORAGE_LIMIT');
|
||||
|
|
@ -391,7 +419,7 @@ App::post('/v1/storage/buckets/:bucketId/files')
|
|||
$fileSize = (\is_array($file['size']) && isset($file['size'][0])) ? $file['size'][0] : $file['size'];
|
||||
|
||||
$contentRange = $request->getHeader('content-range');
|
||||
$fileId = $fileId === 'unique()' ? $dbForProject->getId() : $fileId;
|
||||
$fileId = $fileId === 'unique()' ? ID::unique() : $fileId;
|
||||
$chunk = 1;
|
||||
$chunks = 1;
|
||||
|
||||
|
|
@ -440,13 +468,7 @@ App::post('/v1/storage/buckets/:bucketId/files')
|
|||
$path = $deviceFiles->getPath($fileId . '.' . \pathinfo($fileName, PATHINFO_EXTENSION));
|
||||
$path = str_ireplace($deviceFiles->getRoot(), $deviceFiles->getRoot() . DIRECTORY_SEPARATOR . $bucket->getId(), $path); // Add bucket id to path after root
|
||||
|
||||
if ($permissionBucket) {
|
||||
$file = Authorization::skip(function () use ($dbForProject, $bucket, $fileId) {
|
||||
return $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
|
||||
});
|
||||
} else {
|
||||
$file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
|
||||
}
|
||||
$file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
|
||||
|
||||
$metadata = ['content_type' => $deviceLocal->getFileMimeType($fileTmpName)];
|
||||
if (!$file->isEmpty()) {
|
||||
|
|
@ -462,8 +484,6 @@ App::post('/v1/storage/buckets/:bucketId/files')
|
|||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed uploading file');
|
||||
}
|
||||
|
||||
$read = (is_null($read) && !$user->isEmpty()) ? ['user:' . $user->getId()] : $read ?? [];
|
||||
$write = (is_null($write) && !$user->isEmpty()) ? ['user:' . $user->getId()] : $write ?? [];
|
||||
if ($chunksUploaded === $chunks) {
|
||||
if (App::getEnv('_APP_STORAGE_ANTIVIRUS') === 'enabled' && $bucket->getAttribute('antivirus', true) && $fileSize <= APP_LIMIT_ANTIVIRUS && App::getEnv('_APP_STORAGE_DEVICE', Storage::DEVICE_LOCAL) === Storage::DEVICE_LOCAL) {
|
||||
$antivirus = new Network(
|
||||
|
|
@ -517,8 +537,7 @@ App::post('/v1/storage/buckets/:bucketId/files')
|
|||
if ($file->isEmpty()) {
|
||||
$doc = new Document([
|
||||
'$id' => $fileId,
|
||||
'$read' => $read,
|
||||
'$write' => $write,
|
||||
'$permissions' => $permissions,
|
||||
'bucketId' => $bucket->getId(),
|
||||
'name' => $fileName,
|
||||
'path' => $path,
|
||||
|
|
@ -537,15 +556,11 @@ App::post('/v1/storage/buckets/:bucketId/files')
|
|||
'search' => implode(' ', [$fileId, $fileName]),
|
||||
'metadata' => $metadata,
|
||||
]);
|
||||
if ($permissionBucket) {
|
||||
$file = Authorization::skip(fn () => $dbForProject->createDocument('bucket_' . $bucket->getInternalId(), $doc));
|
||||
} else {
|
||||
$file = $dbForProject->createDocument('bucket_' . $bucket->getInternalId(), $doc);
|
||||
}
|
||||
|
||||
$file = $dbForProject->createDocument('bucket_' . $bucket->getInternalId(), $doc);
|
||||
} else {
|
||||
$file = $file
|
||||
->setAttribute('$read', $read)
|
||||
->setAttribute('$write', $write)
|
||||
->setAttribute('$permissions', $permissions)
|
||||
->setAttribute('signature', $fileHash)
|
||||
->setAttribute('mimeType', $mimeType)
|
||||
->setAttribute('sizeActual', $sizeActual)
|
||||
|
|
@ -557,24 +572,21 @@ App::post('/v1/storage/buckets/:bucketId/files')
|
|||
->setAttribute('metadata', $metadata)
|
||||
->setAttribute('chunksUploaded', $chunksUploaded);
|
||||
|
||||
if ($permissionBucket) {
|
||||
$file = Authorization::skip(fn () => $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file));
|
||||
} else {
|
||||
$file = $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file);
|
||||
}
|
||||
$file = $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file);
|
||||
}
|
||||
} catch (AuthorizationException) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
} catch (StructureException $exception) {
|
||||
throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, $exception->getMessage());
|
||||
} catch (DuplicateException $exception) {
|
||||
} catch (DuplicateException) {
|
||||
throw new Exception(Exception::DOCUMENT_ALREADY_EXISTS);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
if ($file->isEmpty()) {
|
||||
$doc = new Document([
|
||||
'$id' => $fileId,
|
||||
'$read' => $read,
|
||||
'$write' => $write,
|
||||
'$id' => ID::custom($fileId),
|
||||
'$permissions' => $permissions,
|
||||
'bucketId' => $bucket->getId(),
|
||||
'name' => $fileName,
|
||||
'path' => $path,
|
||||
|
|
@ -589,25 +601,20 @@ App::post('/v1/storage/buckets/:bucketId/files')
|
|||
'search' => implode(' ', [$fileId, $fileName]),
|
||||
'metadata' => $metadata,
|
||||
]);
|
||||
if ($permissionBucket) {
|
||||
$file = Authorization::skip(fn () => $dbForProject->createDocument('bucket_' . $bucket->getInternalId(), $doc));
|
||||
} else {
|
||||
$file = $dbForProject->createDocument('bucket_' . $bucket->getInternalId(), $doc);
|
||||
}
|
||||
|
||||
$file = $dbForProject->createDocument('bucket_' . $bucket->getInternalId(), $doc);
|
||||
} else {
|
||||
$file = $file
|
||||
->setAttribute('chunksUploaded', $chunksUploaded)
|
||||
->setAttribute('metadata', $metadata);
|
||||
|
||||
if ($permissionBucket) {
|
||||
$file = Authorization::skip(fn () => $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file));
|
||||
} else {
|
||||
$file = $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file);
|
||||
}
|
||||
$file = $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file);
|
||||
}
|
||||
} catch (AuthorizationException) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
} catch (StructureException $exception) {
|
||||
throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, $exception->getMessage());
|
||||
} catch (DuplicateException $exception) {
|
||||
} catch (DuplicateException) {
|
||||
throw new Exception(Exception::DOCUMENT_ALREADY_EXISTS);
|
||||
}
|
||||
}
|
||||
|
|
@ -649,7 +656,7 @@ App::get('/v1/storage/buckets/:bucketId/files')
|
|||
->param('offset', 0, new Range(0, APP_LIMIT_COUNT), 'Offset value. The default value is 0. Use this param to manage pagination. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
|
||||
->param('cursor', '', new UID(), 'ID of the file used as the starting point for the query, excluding the file itself. Should be used for efficient pagination when working with large sets of data. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
|
||||
->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor, can be either \'before\' or \'after\'.', true)
|
||||
->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true)
|
||||
->param('orderType', Database::ORDER_ASC, new WhiteList([Database::ORDER_ASC, Database::ORDER_DESC], true), 'Order result by ' . Database::ORDER_ASC . ' or ' . Database::ORDER_DESC . ' order.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('mode')
|
||||
|
|
@ -657,54 +664,52 @@ App::get('/v1/storage/buckets/:bucketId/files')
|
|||
|
||||
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
||||
|
||||
if (
|
||||
$bucket->isEmpty()
|
||||
|| (!$bucket->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)
|
||||
) {
|
||||
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)) {
|
||||
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
|
||||
}
|
||||
|
||||
// Check bucket permissions when enforced
|
||||
if ($bucket->getAttribute('permission') === 'bucket') {
|
||||
$validator = new Authorization('read');
|
||||
if (!$validator->isValid($bucket->getRead())) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
$fileSecurity = $bucket->getAttribute('fileSecurity', false);
|
||||
$validator = new Authorization(Database::PERMISSION_READ);
|
||||
$valid = $validator->isValid($bucket->getRead());
|
||||
if (!$fileSecurity && !$valid) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
$queries = [new Query('bucketId', Query::TYPE_EQUAL, [$bucketId])];
|
||||
$filterQueries = [];
|
||||
|
||||
if ($search) {
|
||||
$queries[] = [new Query('name', Query::TYPE_SEARCH, [$search])];
|
||||
}
|
||||
|
||||
if (!empty($cursor)) {
|
||||
if ($bucket->getAttribute('permission') === 'bucket') {
|
||||
$cursorFile = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $cursor));
|
||||
} else {
|
||||
$cursorFile = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $cursor);
|
||||
}
|
||||
|
||||
if ($cursorFile->isEmpty()) {
|
||||
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "File '{$cursor}' for the 'cursor' value not found.");
|
||||
}
|
||||
if (!empty($search)) {
|
||||
$filterQueries[] = Query::search('name', $search);
|
||||
}
|
||||
|
||||
$queries = [];
|
||||
$queries[] = Query::limit($limit);
|
||||
$queries[] = Query::offset($offset);
|
||||
$queries[] = $orderType === Database::ORDER_ASC ? Query::orderAsc('') : Query::orderDesc('');
|
||||
if (!empty($cursor)) {
|
||||
if ($fileSecurity && !$valid) {
|
||||
$cursorDocument = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $cursor);
|
||||
} else {
|
||||
$cursorDocument = Authorization::skip(fn() => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $cursor));
|
||||
}
|
||||
|
||||
if (!empty($search)) {
|
||||
$queries[] = new Query('search', Query::TYPE_SEARCH, [$search]);
|
||||
if ($cursorDocument->isEmpty()) {
|
||||
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "File '{$cursor}' for the 'cursor' value not found.");
|
||||
}
|
||||
|
||||
$queries[] = $cursorDirection === Database::CURSOR_AFTER ? Query::cursorAfter($cursorDocument) : Query::cursorBefore($cursorDocument);
|
||||
}
|
||||
|
||||
if ($bucket->getAttribute('permission') === 'bucket') {
|
||||
$files = Authorization::skip(fn () => $dbForProject->find('bucket_' . $bucket->getInternalId(), $queries, $limit, $offset, [], [$orderType], $cursorFile ?? null, $cursorDirection));
|
||||
if ($fileSecurity && !$valid) {
|
||||
$files = $dbForProject->find('bucket_' . $bucket->getInternalId(), \array_merge($filterQueries, $queries));
|
||||
$total = $dbForProject->count('bucket_' . $bucket->getInternalId(), $filterQueries, APP_LIMIT_COUNT);
|
||||
} else {
|
||||
$files = $dbForProject->find('bucket_' . $bucket->getInternalId(), $queries, $limit, $offset, [], [$orderType], $cursorFile ?? null, $cursorDirection);
|
||||
$files = Authorization::skip(fn () => $dbForProject->find('bucket_' . $bucket->getInternalId(), \array_merge($filterQueries, $queries)));
|
||||
$total = Authorization::skip(fn () => $dbForProject->count('bucket_' . $bucket->getInternalId(), $filterQueries, APP_LIMIT_COUNT));
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'files' => $files,
|
||||
'total' => $dbForProject->count('bucket_' . $bucket->getInternalId(), $queries, APP_LIMIT_COUNT),
|
||||
'total' => $total,
|
||||
]), Response::MODEL_FILE_LIST);
|
||||
});
|
||||
|
||||
|
|
@ -731,28 +736,24 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId')
|
|||
|
||||
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
||||
|
||||
if (
|
||||
$bucket->isEmpty()
|
||||
|| (!$bucket->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)
|
||||
) {
|
||||
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)) {
|
||||
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
|
||||
}
|
||||
|
||||
// Check bucket permissions when enforced
|
||||
if ($bucket->getAttribute('permission') === 'bucket') {
|
||||
$validator = new Authorization('read');
|
||||
if (!$validator->isValid($bucket->getRead())) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
$fileSecurity = $bucket->getAttribute('fileSecurity', false);
|
||||
$validator = new Authorization(Database::PERMISSION_READ);
|
||||
$valid = $validator->isValid($bucket->getRead());
|
||||
if (!$fileSecurity && !$valid) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
if ($bucket->getAttribute('permission') === 'bucket') {
|
||||
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
|
||||
} else {
|
||||
if ($fileSecurity && !$valid) {
|
||||
$file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
|
||||
} else {
|
||||
$file = Authorization::skip(fn() => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
|
||||
}
|
||||
|
||||
if ($file->isEmpty() || $file->getAttribute('bucketId') !== $bucketId) {
|
||||
if ($file->isEmpty()) {
|
||||
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
|
||||
}
|
||||
|
||||
|
|
@ -803,19 +804,15 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
|
|||
|
||||
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
||||
|
||||
if (
|
||||
$bucket->isEmpty()
|
||||
|| (!$bucket->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)
|
||||
) {
|
||||
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)) {
|
||||
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
|
||||
}
|
||||
|
||||
// Check bucket permissions when enforced
|
||||
if ($bucket->getAttribute('permission') === 'bucket') {
|
||||
$validator = new Authorization('read');
|
||||
if (!$validator->isValid($bucket->getRead())) {
|
||||
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND, 'Unauthorized permissions');
|
||||
}
|
||||
$fileSecurity = $bucket->getAttribute('fileSecurity', false);
|
||||
$validator = new Authorization(Database::PERMISSION_READ);
|
||||
$valid = $validator->isValid($bucket->getRead());
|
||||
if (!$fileSecurity && !$valid) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
if ((\strpos($request->getAccept(), 'image/webp') === false) && ('webp' === $output)) { // Fallback webp to jpeg when no browser support
|
||||
|
|
@ -826,14 +823,16 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
|
|||
$outputs = Config::getParam('storage-outputs');
|
||||
$fileLogos = Config::getParam('storage-logos');
|
||||
|
||||
if ($bucket->getAttribute('permission') === 'bucket') {
|
||||
// skip authorization
|
||||
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
|
||||
} else {
|
||||
$date = \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT'; // 45 days cache
|
||||
$key = \md5($fileId . $width . $height . $gravity . $quality . $borderWidth . $borderColor . $borderRadius . $opacity . $rotation . $background . $output);
|
||||
|
||||
if ($fileSecurity && !$valid) {
|
||||
$file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
|
||||
} else {
|
||||
$file = Authorization::skip(fn() => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
|
||||
}
|
||||
|
||||
if ($file->isEmpty() || $file->getAttribute('bucketId') !== $bucketId) {
|
||||
if ($file->isEmpty()) {
|
||||
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
|
||||
}
|
||||
|
||||
|
|
@ -950,28 +949,24 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download')
|
|||
|
||||
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
||||
|
||||
if (
|
||||
$bucket->isEmpty()
|
||||
|| (!$bucket->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)
|
||||
) {
|
||||
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)) {
|
||||
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
|
||||
}
|
||||
|
||||
// Check bucket permissions when enforced
|
||||
if ($bucket->getAttribute('permission') === 'bucket') {
|
||||
$validator = new Authorization('read');
|
||||
if (!$validator->isValid($bucket->getRead())) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
$fileSecurity = $bucket->getAttribute('fileSecurity', false);
|
||||
$validator = new Authorization(Database::PERMISSION_READ);
|
||||
$valid = $validator->isValid($bucket->getRead());
|
||||
if (!$fileSecurity && !$valid) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
if ($bucket->getAttribute('permission') === 'bucket') {
|
||||
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
|
||||
} else {
|
||||
if ($fileSecurity && !$valid) {
|
||||
$file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
|
||||
} else {
|
||||
$file = Authorization::skip(fn() => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
|
||||
}
|
||||
|
||||
if ($file->isEmpty() || $file->getAttribute('bucketId') !== $bucketId) {
|
||||
if ($file->isEmpty()) {
|
||||
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
|
||||
}
|
||||
|
||||
|
|
@ -1085,41 +1080,35 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view')
|
|||
|
||||
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
||||
|
||||
if (
|
||||
$bucket->isEmpty()
|
||||
|| (!$bucket->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)
|
||||
) {
|
||||
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)) {
|
||||
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
|
||||
}
|
||||
|
||||
// Check bucket permissions when enforced
|
||||
if ($bucket->getAttribute('permission') === 'bucket') {
|
||||
$validator = new Authorization('read');
|
||||
if (!$validator->isValid($bucket->getRead())) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
$fileSecurity = $bucket->getAttribute('fileSecurity', false);
|
||||
$validator = new Authorization(Database::PERMISSION_READ);
|
||||
$valid = $validator->isValid($bucket->getRead());
|
||||
if (!$fileSecurity && !$valid) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
if ($bucket->getAttribute('permission') === 'bucket') {
|
||||
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
|
||||
} else {
|
||||
if ($fileSecurity && !$valid) {
|
||||
$file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
|
||||
} else {
|
||||
$file = Authorization::skip(fn() => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
|
||||
}
|
||||
|
||||
if ($file->isEmpty()) {
|
||||
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
|
||||
}
|
||||
|
||||
$mimes = Config::getParam('storage-mimes');
|
||||
|
||||
if ($file->isEmpty() || $file->getAttribute('bucketId') !== $bucketId) {
|
||||
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
|
||||
}
|
||||
|
||||
$path = $file->getAttribute('path', '');
|
||||
|
||||
if (!$deviceFiles->exists($path)) {
|
||||
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND, 'File not found in ' . $path);
|
||||
}
|
||||
|
||||
$compressor = new GZIP();
|
||||
|
||||
$contentType = 'text/plain';
|
||||
|
||||
if (\in_array($file->getAttribute('mimeType'), $mimes)) {
|
||||
|
|
@ -1217,6 +1206,8 @@ App::put('/v1/storage/buckets/:bucketId/files/:fileId')
|
|||
->label('audits.resource', 'files/{response.$id}')
|
||||
->label('usage.metric', 'files.{scope}.requests.update')
|
||||
->label('usage.params', ['bucketId:{request.bucketId}'])
|
||||
->label('abuse-limit', 60)
|
||||
->label('abuse-time', 60)
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'storage')
|
||||
->label('sdk.method', 'updateFile')
|
||||
|
|
@ -1226,68 +1217,76 @@ App::put('/v1/storage/buckets/:bucketId/files/:fileId')
|
|||
->label('sdk.response.model', Response::MODEL_FILE)
|
||||
->param('bucketId', null, new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).')
|
||||
->param('fileId', '', new UID(), 'File unique ID.')
|
||||
->param('read', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE), '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.', true)
|
||||
->param('write', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE), '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.', true)
|
||||
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of permission string. By default the current permissions are inherited. [Learn more about permissions](/docs/permissions).', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('user')
|
||||
->inject('mode')
|
||||
->inject('events')
|
||||
->action(function (string $bucketId, string $fileId, ?array $read, ?array $write, Response $response, Database $dbForProject, Document $user, string $mode, Event $events) {
|
||||
->action(function (string $bucketId, string $fileId, ?array $permissions, Response $response, Database $dbForProject, Document $user, 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
|
||||
$write = (is_null($write) && !$user->isEmpty()) ? ['user:' . $user->getId()] : $write ?? [];
|
||||
|
||||
// Users can only add their roles to files, API keys and Admin users can add any
|
||||
$roles = Authorization::getRoles();
|
||||
|
||||
if (!Auth::isAppUser($roles) && !Auth::isPrivilegedUser($roles)) {
|
||||
foreach ($read as $role) {
|
||||
if (!Authorization::isRole($role)) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED, 'Read permissions must be one of: (' . \implode(', ', $roles) . ')', 400);
|
||||
}
|
||||
}
|
||||
foreach ($write as $role) {
|
||||
if (!Authorization::isRole($role)) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED, 'Write permissions must be one of: (' . \implode(', ', $roles) . ')', 400);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
$bucket->isEmpty()
|
||||
|| (!$bucket->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)
|
||||
) {
|
||||
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)) {
|
||||
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
|
||||
}
|
||||
|
||||
// Check bucket permissions when enforced
|
||||
if ($bucket->getAttribute('permission') === 'bucket') {
|
||||
$validator = new Authorization('write');
|
||||
if (!$validator->isValid($bucket->getWrite())) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
$fileSecurity = $bucket->getAttributes('fileSecurity', false);
|
||||
$validator = new Authorization(Database::PERMISSION_UPDATE);
|
||||
$valid = $validator->isValid($bucket->getUpdate());
|
||||
if (!$fileSecurity && !$valid) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
if ($bucket->getAttribute('permission') === 'bucket') {
|
||||
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
|
||||
} else {
|
||||
$file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
|
||||
}
|
||||
// Read permission should not be required for update
|
||||
$file = Authorization::skip(fn() => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
|
||||
|
||||
if ($file->isEmpty() || $file->getAttribute('bucketId') !== $bucketId) {
|
||||
if ($file->isEmpty()) {
|
||||
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
|
||||
}
|
||||
|
||||
$file
|
||||
->setAttribute('$read', $read)
|
||||
->setAttribute('$write', $write)
|
||||
;
|
||||
// Map aggregate permissions into the multiple permissions they represent.
|
||||
$permissions = Permission::aggregate($permissions, [
|
||||
Database::PERMISSION_READ,
|
||||
Database::PERMISSION_UPDATE,
|
||||
Database::PERMISSION_DELETE,
|
||||
]);
|
||||
|
||||
if ($bucket->getAttribute('permission') === 'bucket') {
|
||||
$file = Authorization::skip(fn () => $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file));
|
||||
// Users can only manage their own roles, API keys and Admin users can manage any
|
||||
$roles = Authorization::getRoles();
|
||||
if (!Auth::isAppUser($roles) && !Auth::isPrivilegedUser($roles) && !\is_null($permissions)) {
|
||||
foreach (Database::PERMISSIONS as $type) {
|
||||
foreach ($permissions as $permission) {
|
||||
$permission = Permission::parse($permission);
|
||||
if ($permission->getPermission() != $type) {
|
||||
continue;
|
||||
}
|
||||
$role = (new Role(
|
||||
$permission->getRole(),
|
||||
$permission->getIdentifier(),
|
||||
$permission->getDimension()
|
||||
))->toString();
|
||||
if (!Authorization::isRole($role)) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED, 'Permissions must be one of: (' . \implode(', ', $roles) . ')');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (\is_null($permissions)) {
|
||||
$permissions = $file->getPermissions() ?? [];
|
||||
}
|
||||
|
||||
$file->setAttribute('$permissions', $permissions);
|
||||
|
||||
if ($fileSecurity && !$valid) {
|
||||
try {
|
||||
$file = $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file);
|
||||
} catch (AuthorizationException) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
} else {
|
||||
$file = $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file);
|
||||
$file = Authorization::skip(fn() => $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file));
|
||||
}
|
||||
|
||||
$events
|
||||
|
|
@ -1308,6 +1307,8 @@ App::delete('/v1/storage/buckets/:bucketId/files/:fileId')
|
|||
->label('audits.resource', 'file/{request.fileId}')
|
||||
->label('usage.metric', 'files.{scope}.requests.delete')
|
||||
->label('usage.params', ['bucketId:{request.bucketId}'])
|
||||
->label('abuse-limit', 60)
|
||||
->label('abuse-time', 60)
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'storage')
|
||||
->label('sdk.method', 'deleteFile')
|
||||
|
|
@ -1325,31 +1326,29 @@ App::delete('/v1/storage/buckets/:bucketId/files/:fileId')
|
|||
->action(function (string $bucketId, string $fileId, Response $response, Database $dbForProject, Event $events, string $mode, Device $deviceFiles, Delete $deletes) {
|
||||
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
||||
|
||||
if (
|
||||
$bucket->isEmpty()
|
||||
|| (!$bucket->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)
|
||||
) {
|
||||
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)) {
|
||||
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
|
||||
}
|
||||
|
||||
// Check bucket permissions when enforced
|
||||
if ($bucket->getAttribute('permission') === 'bucket') {
|
||||
$validator = new Authorization('write');
|
||||
if (!$validator->isValid($bucket->getWrite())) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
$fileSecurity = $bucket->getAttributes('fileSecurity', false);
|
||||
$validator = new Authorization(Database::PERMISSION_DELETE);
|
||||
$valid = $validator->isValid($bucket->getDelete());
|
||||
if (!$fileSecurity && !$valid) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
if ($bucket->getAttribute('permission') === 'bucket') {
|
||||
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
|
||||
} else {
|
||||
$file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
|
||||
}
|
||||
// Read permission should not be required for delete
|
||||
$file = Authorization::skip(fn() => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
|
||||
|
||||
if ($file->isEmpty() || $file->getAttribute('bucketId') !== $bucketId) {
|
||||
if ($file->isEmpty()) {
|
||||
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
|
||||
}
|
||||
|
||||
// Make sure we don't delete the file before the document permission check occurs
|
||||
if ($fileSecurity && !$valid && !$validator->isValid($file->getDelete())) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
$deviceDeleted = false;
|
||||
if ($file->getAttribute('chunksTotal') !== $file->getAttribute('chunksUploaded')) {
|
||||
$deviceDeleted = $deviceFiles->abort(
|
||||
|
|
@ -1366,11 +1365,16 @@ App::delete('/v1/storage/buckets/:bucketId/files/:fileId')
|
|||
->setResource('file/' . $fileId)
|
||||
;
|
||||
|
||||
if ($bucket->getAttribute('permission') === 'bucket') {
|
||||
$deleted = Authorization::skip(fn () => $dbForProject->deleteDocument('bucket_' . $bucket->getInternalId(), $fileId));
|
||||
if ($fileSecurity && !$valid) {
|
||||
try {
|
||||
$deleted = $dbForProject->deleteDocument('bucket_' . $bucket->getInternalId(), $fileId);
|
||||
} catch (AuthorizationException) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
} else {
|
||||
$deleted = $dbForProject->deleteDocument('bucket_' . $bucket->getInternalId(), $fileId);
|
||||
$deleted = Authorization::skip(fn() => $dbForProject->deleteDocument('bucket_' . $bucket->getInternalId(), $fileId));
|
||||
}
|
||||
|
||||
if (!$deleted) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove file from DB');
|
||||
}
|
||||
|
|
@ -1447,9 +1451,11 @@ App::get('/v1/storage/usage')
|
|||
$period = $periods[$range]['period'];
|
||||
|
||||
$requestDocs = $dbForProject->find('stats', [
|
||||
new Query('period', Query::TYPE_EQUAL, [$period]),
|
||||
new Query('metric', Query::TYPE_EQUAL, [$metric]),
|
||||
], $limit, 0, ['time'], [Database::ORDER_DESC]);
|
||||
Query::equal('period', [$period]),
|
||||
Query::equal('metric', [$metric]),
|
||||
Query::limit($limit),
|
||||
Query::orderDesc('time'),
|
||||
]);
|
||||
|
||||
$stats[$metric] = [];
|
||||
foreach ($requestDocs as $requestDoc) {
|
||||
|
|
@ -1469,7 +1475,7 @@ App::get('/v1/storage/usage')
|
|||
};
|
||||
$stats[$metric][] = [
|
||||
'value' => 0,
|
||||
'date' => ($stats[$metric][$last]['date'] ?? \time()) - $diff, // time of last metric minus period
|
||||
'date' => DateTime::addSeconds(new \DateTime($stats[$metric][$last]['date'] ?? null), -1 * $diff),
|
||||
];
|
||||
$backfill--;
|
||||
}
|
||||
|
|
@ -1555,9 +1561,11 @@ App::get('/v1/storage/:bucketId/usage')
|
|||
$limit = $periods[$range]['limit'];
|
||||
$period = $periods[$range]['period'];
|
||||
$requestDocs = $dbForProject->find('stats', [
|
||||
new Query('period', Query::TYPE_EQUAL, [$period]),
|
||||
new Query('metric', Query::TYPE_EQUAL, [$metric]),
|
||||
], $limit, 0, ['time'], [Database::ORDER_DESC]);
|
||||
Query::equal('period', [$period]),
|
||||
Query::equal('metric', [$metric]),
|
||||
Query::limit($limit),
|
||||
Query::orderDesc('time'),
|
||||
]);
|
||||
|
||||
$stats[$metric] = [];
|
||||
foreach ($requestDocs as $requestDoc) {
|
||||
|
|
@ -1577,7 +1585,7 @@ App::get('/v1/storage/:bucketId/usage')
|
|||
};
|
||||
$stats[$metric][] = [
|
||||
'value' => 0,
|
||||
'date' => ($stats[$metric][$last]['date'] ?? \time()) - $diff, // time of last metric minus period
|
||||
'date' => DateTime::addSeconds(new \DateTime($stats[$metric][$last]['date'] ?? null), -1 * $diff),
|
||||
];
|
||||
$backfill--;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,12 +15,17 @@ use Appwrite\Utopia\Response;
|
|||
use MaxMind\Db\Reader;
|
||||
use Utopia\App;
|
||||
use Utopia\Audit\Audit;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Authorization as AuthorizationException;
|
||||
use Utopia\Database\Exception\Duplicate;
|
||||
use Utopia\Database\ID;
|
||||
use Utopia\Database\Permission;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Role;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\UID;
|
||||
|
|
@ -55,29 +60,42 @@ App::post('/v1/teams')
|
|||
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
|
||||
$isAppUser = Auth::isAppUser(Authorization::getRoles());
|
||||
|
||||
$teamId = $teamId == 'unique()' ? $dbForProject->getId() : $teamId;
|
||||
$teamId = $teamId == 'unique()' ? ID::unique() : $teamId;
|
||||
$team = Authorization::skip(fn() => $dbForProject->createDocument('teams', new Document([
|
||||
'$id' => $teamId ,
|
||||
'$read' => ['team:' . $teamId],
|
||||
'$write' => ['team:' . $teamId . '/owner'],
|
||||
'$id' => $teamId,
|
||||
'$permissions' => [
|
||||
Permission::read(Role::team($teamId)),
|
||||
Permission::update(Role::team($teamId, 'owner')),
|
||||
Permission::delete(Role::team($teamId, 'owner')),
|
||||
],
|
||||
'name' => $name,
|
||||
'total' => ($isPrivilegedUser || $isAppUser) ? 0 : 1,
|
||||
'search' => implode(' ', [$teamId, $name]),
|
||||
])));
|
||||
|
||||
if (!$isPrivilegedUser && !$isAppUser) { // Don't add user on server mode
|
||||
$membershipId = $dbForProject->getId();
|
||||
if (!\in_array('owner', $roles)) {
|
||||
$roles[] = 'owner';
|
||||
}
|
||||
|
||||
$membershipId = ID::unique();
|
||||
$membership = new Document([
|
||||
'$id' => $membershipId,
|
||||
'$read' => ['user:' . $user->getId(), 'team:' . $team->getId()],
|
||||
'$write' => ['user:' . $user->getId(), 'team:' . $team->getId() . '/owner'],
|
||||
'$permissions' => [
|
||||
Permission::read(Role::user($user->getId())),
|
||||
Permission::read(Role::team($team->getId())),
|
||||
Permission::update(Role::user($user->getId())),
|
||||
Permission::update(Role::team($team->getId(), 'owner')),
|
||||
Permission::delete(Role::user($user->getId())),
|
||||
Permission::delete(Role::team($team->getId(), 'owner')),
|
||||
],
|
||||
'userId' => $user->getId(),
|
||||
'userInternalId' => $user->getInternalId(),
|
||||
'teamId' => $team->getId(),
|
||||
'teamInternalId' => $team->getInternalId(),
|
||||
'roles' => $roles,
|
||||
'invited' => \time(),
|
||||
'joined' => \time(),
|
||||
'invited' => DateTime::now(),
|
||||
'joined' => DateTime::now(),
|
||||
'confirm' => true,
|
||||
'secret' => '',
|
||||
'search' => implode(' ', [$membershipId, $user->getId()])
|
||||
|
|
@ -118,22 +136,28 @@ App::get('/v1/teams')
|
|||
->inject('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);
|
||||
$filterQueries = [];
|
||||
|
||||
if ($cursorTeam->isEmpty()) {
|
||||
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Team '{$cursor}' for the 'cursor' value not found.");
|
||||
}
|
||||
if (!empty($search)) {
|
||||
$filterQueries[] = Query::search('search', $search);
|
||||
}
|
||||
|
||||
$queries = [];
|
||||
$queries[] = Query::limit($limit);
|
||||
$queries[] = Query::offset($offset);
|
||||
$queries[] = $orderType === Database::ORDER_ASC ? Query::orderAsc('') : Query::orderDesc('');
|
||||
if (!empty($cursor)) {
|
||||
$cursorDocument = $dbForProject->getDocument('teams', $cursor);
|
||||
|
||||
if (!empty($search)) {
|
||||
$queries[] = new Query('search', Query::TYPE_SEARCH, [$search]);
|
||||
if ($cursorDocument->isEmpty()) {
|
||||
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Team '{$cursor}' for the 'cursor' value not found.");
|
||||
}
|
||||
|
||||
$queries[] = $cursorDirection === Database::CURSOR_AFTER ? Query::cursorAfter($cursorDocument) : Query::cursorBefore($cursorDocument);
|
||||
}
|
||||
|
||||
$results = $dbForProject->find('teams', $queries, $limit, $offset, [], [$orderType], $cursorTeam ?? null, $cursorDirection);
|
||||
$total = $dbForProject->count('teams', $queries, APP_LIMIT_COUNT);
|
||||
$results = $dbForProject->find('teams', \array_merge($filterQueries, $queries));
|
||||
$total = $dbForProject->count('teams', $filterQueries, APP_LIMIT_COUNT);
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'teams' => $results,
|
||||
|
|
@ -227,8 +251,9 @@ App::delete('/v1/teams/:teamId')
|
|||
}
|
||||
|
||||
$memberships = $dbForProject->find('memberships', [
|
||||
new Query('teamId', Query::TYPE_EQUAL, [$teamId]),
|
||||
], 2000, 0); // TODO fix members limit
|
||||
Query::equal('teamId', [$teamId]),
|
||||
Query::limit(2000), // TODO fix members limit
|
||||
]);
|
||||
|
||||
// TODO delete all members individually from the user object
|
||||
foreach ($memberships as $membership) {
|
||||
|
|
@ -298,7 +323,7 @@ App::post('/v1/teams/:teamId/memberships')
|
|||
throw new Exception(Exception::TEAM_NOT_FOUND);
|
||||
}
|
||||
|
||||
$invitee = $dbForProject->findOne('users', [new Query('email', Query::TYPE_EQUAL, [$email])]); // Get user by email address
|
||||
$invitee = $dbForProject->findOne('users', [Query::equal('email', [$email])]); // Get user by email address
|
||||
|
||||
if (empty($invitee)) { // Create new user if no user with same email found
|
||||
$limit = $project->getAttribute('auths', [])['limit'] ?? 0;
|
||||
|
|
@ -312,11 +337,15 @@ App::post('/v1/teams/:teamId/memberships')
|
|||
}
|
||||
|
||||
try {
|
||||
$userId = $dbForProject->getId();
|
||||
$userId = ID::unique();
|
||||
$invitee = Authorization::skip(fn() => $dbForProject->createDocument('users', new Document([
|
||||
'$id' => $userId,
|
||||
'$read' => ['user:' . $userId, 'role:all'],
|
||||
'$write' => ['user:' . $userId],
|
||||
'$permissions' => [
|
||||
Permission::read(Role::any()),
|
||||
Permission::read(Role::user($userId)),
|
||||
Permission::update(Role::user($userId)),
|
||||
Permission::delete(Role::user($userId)),
|
||||
],
|
||||
'email' => $email,
|
||||
'emailVerification' => false,
|
||||
'status' => true,
|
||||
|
|
@ -328,8 +357,8 @@ App::post('/v1/teams/:teamId/memberships')
|
|||
* team invite and OAuth to allow password updates without an
|
||||
* old password
|
||||
*/
|
||||
'passwordUpdate' => 0,
|
||||
'registration' => \time(),
|
||||
'passwordUpdate' => null,
|
||||
'registration' => DateTime::now(),
|
||||
'reset' => false,
|
||||
'name' => $name,
|
||||
'prefs' => new \stdClass(),
|
||||
|
|
@ -351,18 +380,23 @@ App::post('/v1/teams/:teamId/memberships')
|
|||
|
||||
$secret = Auth::tokenGenerator();
|
||||
|
||||
$membershipId = $dbForProject->getId();
|
||||
$membershipId = ID::unique();
|
||||
$membership = new Document([
|
||||
'$id' => $membershipId,
|
||||
'$read' => ['role:all'],
|
||||
'$write' => ['user:' . $invitee->getId(), 'team:' . $team->getId() . '/owner'],
|
||||
'$permissions' => [
|
||||
Permission::read(Role::any()),
|
||||
Permission::update(Role::user($invitee->getId())),
|
||||
Permission::update(Role::team($team->getId(), 'owner')),
|
||||
Permission::delete(Role::user($invitee->getId())),
|
||||
Permission::delete(Role::team($team->getId(), 'owner')),
|
||||
],
|
||||
'userId' => $invitee->getId(),
|
||||
'userInternalId' => $invitee->getInternalId(),
|
||||
'teamId' => $team->getId(),
|
||||
'teamInternalId' => $team->getInternalId(),
|
||||
'roles' => $roles,
|
||||
'invited' => \time(),
|
||||
'joined' => ($isPrivilegedUser || $isAppUser) ? \time() : 0,
|
||||
'invited' => DateTime::now(),
|
||||
'joined' => ($isPrivilegedUser || $isAppUser) ? DateTime::now() : null,
|
||||
'confirm' => ($isPrivilegedUser || $isAppUser),
|
||||
'secret' => Auth::hash($secret),
|
||||
'search' => implode(' ', [$membershipId, $invitee->getId()])
|
||||
|
|
@ -435,7 +469,7 @@ App::get('/v1/teams/:teamId/memberships')
|
|||
->param('offset', 0, new Range(0, APP_LIMIT_COUNT), 'Offset value. The default value is 0. Use this value to manage pagination. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
|
||||
->param('cursor', '', new UID(), 'ID of the membership used as the starting point for the query, excluding the membership itself. Should be used for efficient pagination when working with large sets of data. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
|
||||
->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor, can be either \'before\' or \'after\'.', true)
|
||||
->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true)
|
||||
->param('orderType', Database::ORDER_ASC, new WhiteList([Database::ORDER_ASC, Database::ORDER_DESC], true), 'Order result by ' . Database::ORDER_ASC . ' or ' . Database::ORDER_DESC . ' order.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->action(function (string $teamId, string $search, int $limit, int $offset, string $cursor, string $cursorDirection, string $orderType, Response $response, Database $dbForProject) {
|
||||
|
|
@ -446,33 +480,34 @@ App::get('/v1/teams/:teamId/memberships')
|
|||
throw new Exception(Exception::TEAM_NOT_FOUND);
|
||||
}
|
||||
|
||||
if (!empty($cursor)) {
|
||||
$cursorMembership = $dbForProject->getDocument('memberships', $cursor);
|
||||
|
||||
if ($cursorMembership->isEmpty()) {
|
||||
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Membership '{$cursor}' for the 'cursor' value not found.");
|
||||
}
|
||||
}
|
||||
|
||||
$queries = [new Query('teamId', Query::TYPE_EQUAL, [$teamId])];
|
||||
$filterQueries = [Query::equal('teamId', [$teamId])];
|
||||
|
||||
if (!empty($search)) {
|
||||
$queries[] = new Query('search', Query::TYPE_SEARCH, [$search]);
|
||||
$filterQueries[] = Query::search('search', $search);
|
||||
}
|
||||
|
||||
$otherQueries = [];
|
||||
$otherQueries[] = Query::limit($limit);
|
||||
$otherQueries[] = Query::offset($offset);
|
||||
$otherQueries[] = $orderType === Database::ORDER_ASC ? Query::orderAsc('') : Query::orderDesc('');
|
||||
if (!empty($cursor)) {
|
||||
$cursorDocument = $dbForProject->getDocument('memberships', $cursor);
|
||||
|
||||
if ($cursorDocument->isEmpty()) {
|
||||
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Membership '{$cursor}' for the 'cursor' value not found.");
|
||||
}
|
||||
|
||||
$otherQueries[] = $cursorDirection === Database::CURSOR_AFTER ? Query::cursorAfter($cursorDocument) : Query::cursorBefore($cursorDocument);
|
||||
}
|
||||
|
||||
$memberships = $dbForProject->find(
|
||||
collection: 'memberships',
|
||||
queries: $queries,
|
||||
limit: $limit,
|
||||
offset: $offset,
|
||||
orderTypes: [$orderType],
|
||||
cursor: $cursorMembership ?? null,
|
||||
cursorDirection: $cursorDirection
|
||||
queries: \array_merge($filterQueries, $otherQueries),
|
||||
);
|
||||
|
||||
$total = $dbForProject->count(
|
||||
collection:'memberships',
|
||||
queries: $queries,
|
||||
collection: 'memberships',
|
||||
queries: $filterQueries,
|
||||
max: APP_LIMIT_COUNT
|
||||
);
|
||||
|
||||
|
|
@ -670,7 +705,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
|||
}
|
||||
|
||||
$membership // Attach user to team
|
||||
->setAttribute('joined', \time())
|
||||
->setAttribute('joined', DateTime::now())
|
||||
->setAttribute('confirm', true)
|
||||
;
|
||||
|
||||
|
|
@ -678,32 +713,35 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
|||
|
||||
// Log user in
|
||||
|
||||
Authorization::setRole('user:' . $user->getId());
|
||||
Authorization::setRole(Role::user($user->getId())->toString());
|
||||
|
||||
$detector = new Detector($request->getUserAgent('UNKNOWN'));
|
||||
$record = $geodb->get($request->getIP());
|
||||
$expiry = \time() + Auth::TOKEN_EXPIRATION_LOGIN_LONG;
|
||||
$expire = DateTime::addSeconds(new \DateTime(), Auth::TOKEN_EXPIRATION_LOGIN_LONG);
|
||||
$secret = Auth::tokenGenerator();
|
||||
$session = new Document(array_merge([
|
||||
'$id' => $dbForProject->getId(),
|
||||
'$id' => ID::unique(),
|
||||
'userId' => $user->getId(),
|
||||
'userInternalId' => $user->getInternalId(),
|
||||
'provider' => Auth::SESSION_PROVIDER_EMAIL,
|
||||
'providerUid' => $user->getAttribute('email'),
|
||||
'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak
|
||||
'expire' => $expiry,
|
||||
'expire' => $expire,
|
||||
'userAgent' => $request->getUserAgent('UNKNOWN'),
|
||||
'ip' => $request->getIP(),
|
||||
'countryCode' => ($record) ? \strtolower($record['country']['iso_code']) : '--',
|
||||
], $detector->getOS(), $detector->getClient(), $detector->getDevice()));
|
||||
|
||||
$session = $dbForProject->createDocument('sessions', $session
|
||||
->setAttribute('$read', ['user:' . $user->getId()])
|
||||
->setAttribute('$write', ['user:' . $user->getId()]));
|
||||
->setAttribute('$permissions', [
|
||||
Permission::read(Role::user($user->getId())),
|
||||
Permission::update(Role::user($user->getId())),
|
||||
Permission::delete(Role::user($user->getId())),
|
||||
]));
|
||||
|
||||
$dbForProject->deleteCachedDocument('users', $user->getId());
|
||||
|
||||
Authorization::setRole('user:' . $userId);
|
||||
Authorization::setRole(Role::user($userId)->toString());
|
||||
|
||||
$membership = $dbForProject->updateDocument('memberships', $membership->getId(), $membership);
|
||||
|
||||
|
|
@ -723,8 +761,8 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
|||
}
|
||||
|
||||
$response
|
||||
->addCookie(Auth::$cookieName . '_legacy', Auth::encodeSession($user->getId(), $secret), $expiry, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null)
|
||||
->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), $expiry, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite'))
|
||||
->addCookie(Auth::$cookieName . '_legacy', Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null)
|
||||
->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite'))
|
||||
;
|
||||
|
||||
$response->dynamic(
|
||||
|
|
|
|||
|
|
@ -8,13 +8,21 @@ use Appwrite\Event\Delete;
|
|||
use Appwrite\Event\Event;
|
||||
use Appwrite\Network\Validator\Email;
|
||||
use Appwrite\Utopia\Database\Validator\CustomId;
|
||||
use Appwrite\Utopia\Database\Validator\Queries;
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Users;
|
||||
use Appwrite\Utopia\Database\Validator\Query\Limit;
|
||||
use Appwrite\Utopia\Database\Validator\Query\Offset;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\App;
|
||||
use Utopia\Audit\Audit;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\ID;
|
||||
use Utopia\Database\Permission;
|
||||
use Utopia\Database\Role;
|
||||
use Utopia\Locale\Locale;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Exception\Duplicate;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Database\Database;
|
||||
|
|
@ -23,7 +31,6 @@ use Utopia\Database\Validator\Authorization;
|
|||
use Utopia\Validator\Assoc;
|
||||
use Utopia\Validator\WhiteList;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\Boolean;
|
||||
use MaxMind\Db\Reader;
|
||||
use Utopia\Validator\Integer;
|
||||
|
|
@ -38,12 +45,17 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e
|
|||
}
|
||||
|
||||
try {
|
||||
$userId = $userId == 'unique()' ? $dbForProject->getId() : $userId;
|
||||
$userId = $userId == 'unique()'
|
||||
? ID::unique()
|
||||
: ID::custom($userId);
|
||||
|
||||
$user = $dbForProject->createDocument('users', new Document([
|
||||
'$id' => $userId,
|
||||
'$read' => ['role:all'],
|
||||
'$write' => ['user:' . $userId],
|
||||
'$permissions' => [
|
||||
Permission::read(Role::any()),
|
||||
Permission::update(Role::user($userId)),
|
||||
Permission::delete(Role::user($userId)),
|
||||
],
|
||||
'email' => $email,
|
||||
'emailVerification' => false,
|
||||
'phone' => $phone,
|
||||
|
|
@ -52,8 +64,8 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e
|
|||
'password' => (!empty($password)) ? ($hash === 'plaintext' ? Auth::passwordHash($password, $hash, $hashOptionsObject) : $password) : null,
|
||||
'hash' => $hash === 'plaintext' ? Auth::DEFAULT_ALGO : $hash,
|
||||
'hashOptions' => $hash === 'plaintext' ? Auth::DEFAULT_ALGO_OPTIONS : $hashOptions,
|
||||
'passwordUpdate' => (!empty($password)) ? \time() : 0,
|
||||
'registration' => \time(),
|
||||
'passwordUpdate' => (!empty($password)) ? DateTime::now() : null,
|
||||
'registration' => DateTime::now(),
|
||||
'reset' => false,
|
||||
'name' => $name,
|
||||
'prefs' => new \stdClass(),
|
||||
|
|
@ -331,33 +343,40 @@ App::get('/v1/users')
|
|||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_USER_LIST)
|
||||
->param('queries', [], new Users(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Users::ALLOWED_ATTRIBUTES), true)
|
||||
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
|
||||
->param('limit', 25, new Range(0, 100), 'Maximum number of users 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 param to manage pagination. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
|
||||
->param('cursor', '', new UID(), 'ID of the user used as the starting point for the query, excluding the user itself. Should be used for efficient pagination when working with large sets of data. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
|
||||
->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor, can be either \'before\' or \'after\'.', true)
|
||||
->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->action(function (string $search, int $limit, int $offset, string $cursor, string $cursorDirection, string $orderType, Response $response, Database $dbForProject) {
|
||||
->action(function (array $queries, string $search, Response $response, Database $dbForProject) {
|
||||
|
||||
if (!empty($cursor)) {
|
||||
$cursorUser = $dbForProject->getDocument('users', $cursor);
|
||||
|
||||
if ($cursorUser->isEmpty()) {
|
||||
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "User '{$cursor}' for the 'cursor' value not found.");
|
||||
}
|
||||
}
|
||||
|
||||
$queries = [];
|
||||
$queries = Query::parseQueries($queries);
|
||||
|
||||
if (!empty($search)) {
|
||||
$queries[] = new Query('search', Query::TYPE_SEARCH, [$search]);
|
||||
$queries[] = Query::search('search', $search);
|
||||
}
|
||||
|
||||
// Set default limit
|
||||
$queries[] = Query::limit(25);
|
||||
|
||||
// Get cursor document if there was a cursor query
|
||||
$cursor = Query::getByType($queries, Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE)[0] ?? null;
|
||||
if ($cursor !== null) {
|
||||
/** @var Query $cursor */
|
||||
$userId = $cursor->getValue();
|
||||
$cursorDocument = $dbForProject->getDocument('users', $userId);
|
||||
|
||||
if ($cursorDocument->isEmpty()) {
|
||||
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "User '{$userId}' for the 'cursor' value not found.");
|
||||
}
|
||||
|
||||
$cursor->setValue($cursorDocument);
|
||||
}
|
||||
|
||||
$filterQueries = Query::groupByType($queries)['filters'];
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'users' => $dbForProject->find('users', $queries, $limit, $offset, [], [$orderType], $cursorUser ?? null, $cursorDirection),
|
||||
'total' => $dbForProject->count('users', $queries, APP_LIMIT_COUNT),
|
||||
'users' => $dbForProject->find('users', $queries),
|
||||
'total' => $dbForProject->count('users', $filterQueries, APP_LIMIT_COUNT),
|
||||
]), Response::MODEL_USER_LIST);
|
||||
});
|
||||
|
||||
|
|
@ -510,13 +529,12 @@ App::get('/v1/users/:userId/logs')
|
|||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_LOG_LIST)
|
||||
->param('userId', '', new UID(), 'User 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)
|
||||
->param('queries', [], new Queries(new Limit(), new Offset()), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Only supported methods are limit and offset', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('locale')
|
||||
->inject('geodb')
|
||||
->action(function (string $userId, int $limit, int $offset, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) {
|
||||
->action(function (string $userId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) {
|
||||
|
||||
$user = $dbForProject->getDocument('users', $userId);
|
||||
|
||||
|
|
@ -524,6 +542,11 @@ App::get('/v1/users/:userId/logs')
|
|||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$queries = Query::parseQueries($queries);
|
||||
$grouped = Query::groupByType($queries);
|
||||
$limit = $grouped['limit'] ?? 25;
|
||||
$offset = $grouped['offset'] ?? 0;
|
||||
|
||||
$audit = new Audit($dbForProject);
|
||||
|
||||
$logs = $audit->getLogsByUser($user->getId(), $limit, $offset);
|
||||
|
|
@ -606,8 +629,7 @@ App::patch('/v1/users/:userId/status')
|
|||
$user = $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('status', (bool) $status));
|
||||
|
||||
$events
|
||||
->setParam('userId', $user->getId())
|
||||
;
|
||||
->setParam('userId', $user->getId());
|
||||
|
||||
$response->dynamic($user, Response::MODEL_USER);
|
||||
});
|
||||
|
|
@ -642,8 +664,7 @@ App::patch('/v1/users/:userId/verification')
|
|||
$user = $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('emailVerification', $emailVerification));
|
||||
|
||||
$events
|
||||
->setParam('userId', $user->getId())
|
||||
;
|
||||
->setParam('userId', $user->getId());
|
||||
|
||||
$response->dynamic($user, Response::MODEL_USER);
|
||||
});
|
||||
|
|
@ -678,8 +699,7 @@ App::patch('/v1/users/:userId/verification/phone')
|
|||
$user = $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('phoneVerification', $phoneVerification));
|
||||
|
||||
$events
|
||||
->setParam('userId', $user->getId())
|
||||
;
|
||||
->setParam('userId', $user->getId());
|
||||
|
||||
$response->dynamic($user, Response::MODEL_USER);
|
||||
});
|
||||
|
|
@ -756,7 +776,7 @@ App::patch('/v1/users/:userId/password')
|
|||
->setAttribute('password', Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS))
|
||||
->setAttribute('hash', Auth::DEFAULT_ALGO)
|
||||
->setAttribute('hashOptions', Auth::DEFAULT_ALGO_OPTIONS)
|
||||
->setAttribute('passwordUpdate', \time());
|
||||
->setAttribute('passwordUpdate', DateTime::now());
|
||||
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
|
||||
|
||||
|
|
@ -798,8 +818,7 @@ App::patch('/v1/users/:userId/email')
|
|||
$user
|
||||
->setAttribute('email', $email)
|
||||
->setAttribute('emailVerification', false)
|
||||
->setAttribute('search', \implode(' ', [$user->getId(), $email, $user->getAttribute('name', ''), $user->getAttribute('phone', '')]))
|
||||
;
|
||||
->setAttribute('search', \implode(' ', [$user->getId(), $email, $user->getAttribute('name', ''), $user->getAttribute('phone', '')]));
|
||||
|
||||
try {
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
|
||||
|
|
@ -920,8 +939,7 @@ App::patch('/v1/users/:userId/prefs')
|
|||
$user = $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('prefs', $prefs));
|
||||
|
||||
$events
|
||||
->setParam('userId', $user->getId())
|
||||
;
|
||||
->setParam('userId', $user->getId());
|
||||
|
||||
$response->dynamic(new Document($prefs), Response::MODEL_PREFERENCES);
|
||||
});
|
||||
|
|
@ -963,8 +981,7 @@ App::delete('/v1/users/:userId/sessions/:sessionId')
|
|||
|
||||
$events
|
||||
->setParam('userId', $user->getId())
|
||||
->setParam('sessionId', $sessionId)
|
||||
;
|
||||
->setParam('sessionId', $sessionId);
|
||||
|
||||
$response->noContent();
|
||||
});
|
||||
|
|
@ -996,7 +1013,8 @@ App::delete('/v1/users/:userId/sessions')
|
|||
|
||||
$sessions = $user->getAttribute('sessions', []);
|
||||
|
||||
foreach ($sessions as $key => $session) { /** @var Document $session */
|
||||
foreach ($sessions as $key => $session) {
|
||||
/** @var Document $session */
|
||||
$dbForProject->deleteDocument('sessions', $session->getId());
|
||||
//TODO: fix this
|
||||
}
|
||||
|
|
@ -1005,8 +1023,7 @@ App::delete('/v1/users/:userId/sessions')
|
|||
|
||||
$events
|
||||
->setParam('userId', $user->getId())
|
||||
->setPayload($response->output($user, Response::MODEL_USER))
|
||||
;
|
||||
->setPayload($response->output($user, Response::MODEL_USER));
|
||||
|
||||
$response->noContent();
|
||||
});
|
||||
|
|
@ -1044,13 +1061,11 @@ App::delete('/v1/users/:userId')
|
|||
|
||||
$deletes
|
||||
->setType(DELETE_TYPE_DOCUMENT)
|
||||
->setDocument($clone)
|
||||
;
|
||||
->setDocument($clone);
|
||||
|
||||
$events
|
||||
->setParam('userId', $user->getId())
|
||||
->setPayload($response->output($clone, Response::MODEL_USER))
|
||||
;
|
||||
->setPayload($response->output($clone, Response::MODEL_USER));
|
||||
|
||||
$response->noContent();
|
||||
});
|
||||
|
|
@ -1066,7 +1081,7 @@ App::get('/v1/users/usage')
|
|||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_USAGE_USERS)
|
||||
->param('range', '30d', new WhiteList(['24h', '7d', '30d', '90d'], true), 'Date range.', true)
|
||||
->param('provider', '', new WhiteList(\array_merge(['email', 'anonymous'], \array_map(fn($value) => "oauth-" . $value, \array_keys(Config::getParam('providers', [])))), true), 'Provider Name.', true)
|
||||
->param('provider', '', new WhiteList(\array_merge(['email', 'anonymous'], \array_map(fn ($value) => "oauth-" . $value, \array_keys(Config::getParam('providers', [])))), true), 'Provider Name.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('register')
|
||||
|
|
@ -1094,7 +1109,7 @@ App::get('/v1/users/usage')
|
|||
];
|
||||
|
||||
$metrics = [
|
||||
'users.$all.requests.count',
|
||||
'users.$all.count.total',
|
||||
'users.$all.requests.create',
|
||||
'users.$all.requests.read',
|
||||
'users.$all.requests.update',
|
||||
|
|
@ -1112,9 +1127,11 @@ App::get('/v1/users/usage')
|
|||
$period = $periods[$range]['period'];
|
||||
|
||||
$requestDocs = $dbForProject->find('stats', [
|
||||
new Query('period', Query::TYPE_EQUAL, [$period]),
|
||||
new Query('metric', Query::TYPE_EQUAL, [$metric]),
|
||||
], $limit, 0, ['time'], [Database::ORDER_DESC]);
|
||||
Query::equal('period', [$period]),
|
||||
Query::equal('metric', [$metric]),
|
||||
Query::limit($limit),
|
||||
Query::orderDesc('time'),
|
||||
]);
|
||||
|
||||
$stats[$metric] = [];
|
||||
foreach ($requestDocs as $requestDoc) {
|
||||
|
|
@ -1134,7 +1151,7 @@ App::get('/v1/users/usage')
|
|||
};
|
||||
$stats[$metric][] = [
|
||||
'value' => 0,
|
||||
'date' => ($stats[$metric][$last]['date'] ?? \time()) - $diff, // time of last metric minus period
|
||||
'date' => DateTime::addSeconds(new \DateTime($stats[$metric][$last]['date'] ?? null), -1 * $diff),
|
||||
];
|
||||
$backfill--;
|
||||
}
|
||||
|
|
@ -1144,7 +1161,7 @@ App::get('/v1/users/usage')
|
|||
|
||||
$usage = new Document([
|
||||
'range' => $range,
|
||||
'usersCount' => $stats['users.$all.requests.count'] ?? [],
|
||||
'usersCount' => $stats['users.$all.count.total'] ?? [],
|
||||
'usersCreate' => $stats['users.$all.requests.create'] ?? [],
|
||||
'usersRead' => $stats['users.$all.requests.read'] ?? [],
|
||||
'usersUpdate' => $stats['users.$all.requests.update'] ?? [],
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
require_once __DIR__ . '/../init.php';
|
||||
|
||||
use Utopia\App;
|
||||
use Utopia\Database\Role;
|
||||
use Utopia\Locale\Locale;
|
||||
use Utopia\Logger\Logger;
|
||||
use Utopia\Logger\Log;
|
||||
|
|
@ -22,6 +23,7 @@ use Appwrite\Utopia\Response\Filters\V13 as ResponseV13;
|
|||
use Appwrite\Utopia\Response\Filters\V14 as ResponseV14;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
|
|
@ -89,7 +91,7 @@ App::init()
|
|||
if (!empty($envDomain) && $envDomain !== 'localhost') {
|
||||
$mainDomain = $envDomain;
|
||||
} else {
|
||||
$domainDocument = $dbForConsole->findOne('domains', [], 0, ['_id'], ['ASC']);
|
||||
$domainDocument = $dbForConsole->findOne('domains', [Query::orderAsc('_id')]);
|
||||
$mainDomain = $domainDocument ? $domainDocument->getAttribute('domain') : $domain->get();
|
||||
}
|
||||
|
||||
|
|
@ -97,7 +99,7 @@ App::init()
|
|||
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()])
|
||||
Query::equal('domain', [$domain->get()])
|
||||
]);
|
||||
|
||||
if (!$domainDocument) {
|
||||
|
|
@ -245,7 +247,9 @@ App::init()
|
|||
/*
|
||||
* ACL Check
|
||||
*/
|
||||
$role = ($user->isEmpty()) ? Auth::USER_ROLE_GUEST : Auth::USER_ROLE_MEMBER;
|
||||
$role = ($user->isEmpty())
|
||||
? Role::guests()->toString()
|
||||
: Role::users()->toString();
|
||||
|
||||
// Add user roles
|
||||
$memberships = $user->find('teamId', $project->getAttribute('teamId', null), 'memberships');
|
||||
|
|
@ -289,21 +293,20 @@ App::init()
|
|||
'name' => $project->getAttribute('name', 'Untitled'),
|
||||
]);
|
||||
|
||||
$role = Auth::USER_ROLE_APP;
|
||||
$role = Auth::USER_ROLE_APPS;
|
||||
$scopes = \array_merge($roles[$role]['scopes'], $key->getAttribute('scopes', []));
|
||||
|
||||
$expire = $key->getAttribute('expire', 0);
|
||||
|
||||
if (!empty($expire) && $expire < \time()) {
|
||||
$expire = $key->getAttribute('expire');
|
||||
if (!empty($expire) && $expire < DateTime::formatTz(DateTime::now())) {
|
||||
throw new AppwriteException(AppwriteException:: PROJECT_KEY_EXPIRED);
|
||||
}
|
||||
|
||||
Authorization::setRole('role:' . Auth::USER_ROLE_APP);
|
||||
Authorization::setRole(Auth::USER_ROLE_APPS);
|
||||
Authorization::setDefaultStatus(false); // Cancel security segmentation for API keys.
|
||||
}
|
||||
}
|
||||
|
||||
Authorization::setRole('role:' . $role);
|
||||
Authorization::setRole($role);
|
||||
|
||||
foreach (Auth::getRoles($user) as $authRole) {
|
||||
Authorization::setRole($authRole);
|
||||
|
|
|
|||
|
|
@ -282,7 +282,7 @@ App::post('/v1/mock/tests/general/upload')
|
|||
|
||||
if ($end !== $size) {
|
||||
$response->json([
|
||||
'$id' => 'newfileid',
|
||||
'$id' => ID::custom('newfileid'),
|
||||
'chunksTotal' => $file['size'] / $chunkSize,
|
||||
'chunksUploaded' => $start / $chunkSize
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ App::init()
|
|||
throw new Exception(Exception::PROJECT_UNKNOWN);
|
||||
}
|
||||
|
||||
/**
|
||||
/*
|
||||
* Abuse Check
|
||||
*/
|
||||
$abuseKeyLabel = $route->getLabel('abuse-key', 'url:{url},ip:{ip}');
|
||||
|
|
@ -80,10 +80,10 @@ App::init()
|
|||
foreach ($abuseKeyLabel as $abuseKey) {
|
||||
$timeLimit = new TimeLimit($abuseKey, $route->getLabel('abuse-limit', 0), $route->getLabel('abuse-time', 3600), $dbForProject);
|
||||
$timeLimit
|
||||
->setParam('{userId}', $user->getId())
|
||||
->setParam('{userAgent}', $request->getUserAgent(''))
|
||||
->setParam('{ip}', $request->getIP())
|
||||
->setParam('{url}', $request->getHostname() . $route->getPath());
|
||||
->setParam('{userId}', $user->getId())
|
||||
->setParam('{userAgent}', $request->getUserAgent(''))
|
||||
->setParam('{ip}', $request->getIP())
|
||||
->setParam('{url}', $request->getHostname() . $route->getPath());
|
||||
$timeLimitArray[] = $timeLimit;
|
||||
}
|
||||
|
||||
|
|
@ -101,13 +101,17 @@ App::init()
|
|||
}
|
||||
|
||||
$abuse = new Abuse($timeLimit);
|
||||
$remaining = $timeLimit->remaining();
|
||||
$limit = $timeLimit->limit();
|
||||
$time = (new DateTime($timeLimit->time()))->getTimestamp() + $route->getLabel('abuse-time', 3600);
|
||||
|
||||
if ($timeLimit->limit() && ($timeLimit->remaining() < $closestLimit || is_null($closestLimit))) {
|
||||
$closestLimit = $timeLimit->remaining();
|
||||
if ($limit && ($remaining < $closestLimit || is_null($closestLimit))) {
|
||||
$closestLimit = $remaining;
|
||||
$response
|
||||
->addHeader('X-RateLimit-Limit', $timeLimit->limit())
|
||||
->addHeader('X-RateLimit-Remaining', $timeLimit->remaining())
|
||||
->addHeader('X-RateLimit-Reset', $timeLimit->time() + $route->getLabel('abuse-time', 3600));
|
||||
->addHeader('X-RateLimit-Limit', $limit)
|
||||
->addHeader('X-RateLimit-Remaining', $remaining)
|
||||
->addHeader('X-RateLimit-Reset', $time)
|
||||
;
|
||||
}
|
||||
|
||||
if (
|
||||
|
|
@ -119,9 +123,9 @@ App::init()
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Background Jobs
|
||||
*/
|
||||
/*
|
||||
* Background Jobs
|
||||
*/
|
||||
$events
|
||||
->setEvent($route->getLabel('event', ''))
|
||||
->setProject($project)
|
||||
|
|
@ -397,7 +401,7 @@ App::shutdown()
|
|||
&& !empty($route->getLabel('sdk.namespace', null))
|
||||
) { // Don't calculate console usage on admin mode
|
||||
$metric = $route->getLabel('usage.metric', '');
|
||||
$usageParams = $route->getLabel('usage.params', '');
|
||||
$usageParams = $route->getLabel('usage.params', []);
|
||||
|
||||
if (!empty($metric)) {
|
||||
$usage->setParam($metric, 1);
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ use Appwrite\Utopia\Response;
|
|||
use Appwrite\Utopia\View;
|
||||
use Utopia\App;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Domains\Domain;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Storage\Storage;
|
||||
|
|
@ -289,9 +290,22 @@ App::get('/console/databases/collection')
|
|||
])
|
||||
;
|
||||
|
||||
$permissions = new View(__DIR__ . '/../../views/console/comps/permissions-matrix.phtml');
|
||||
$permissions
|
||||
->setParam('method', 'databases.getCollection')
|
||||
->setParam('events', 'load,databases.updateCollection')
|
||||
->setParam('data', 'project-collection')
|
||||
->setParam('params', [
|
||||
'collection-id' => '{{router.params.id}}',
|
||||
'database-id' => '{{router.params.databaseId}}'
|
||||
]);
|
||||
|
||||
$page = new View(__DIR__ . '/../../views/console/databases/collection.phtml');
|
||||
|
||||
$page->setParam('logs', $logs);
|
||||
$page
|
||||
->setParam('permissions', $permissions)
|
||||
->setParam('logs', $logs)
|
||||
;
|
||||
|
||||
$layout
|
||||
->setParam('title', APP_NAME . ' - Database Collection')
|
||||
|
|
@ -326,12 +340,29 @@ App::get('/console/databases/document')
|
|||
])
|
||||
;
|
||||
|
||||
$permissions = new View(__DIR__ . '/../../views/console/comps/permissions-matrix.phtml');
|
||||
|
||||
$permissions
|
||||
->setParam('method', 'databases.getDocument')
|
||||
->setParam('events', 'load,databases.updateDocument')
|
||||
->setParam('data', 'project-document')
|
||||
->setParam('permissions', \array_filter(
|
||||
Database::PERMISSIONS,
|
||||
fn ($perm) => $perm != Database::PERMISSION_CREATE
|
||||
))
|
||||
->setParam('params', [
|
||||
'collection-id' => '{{router.params.collection}}',
|
||||
'database-id' => '{{router.params.databaseId}}',
|
||||
'document-id' => '{{router.params.id}}',
|
||||
]);
|
||||
|
||||
$page = new View(__DIR__ . '/../../views/console/databases/document.phtml');
|
||||
|
||||
$page
|
||||
->setParam('new', false)
|
||||
->setParam('database', $databaseId)
|
||||
->setParam('collection', $collection)
|
||||
->setParam('permissions', $permissions)
|
||||
->setParam('logs', $logs)
|
||||
;
|
||||
|
||||
|
|
@ -349,12 +380,27 @@ App::get('/console/databases/document/new')
|
|||
->inject('layout')
|
||||
->action(function (string $databaseId, string $collection, View $layout) {
|
||||
|
||||
$permissions = new View(__DIR__ . '/../../views/console/comps/permissions-matrix.phtml');
|
||||
|
||||
$permissions
|
||||
->setParam('data', 'project-document')
|
||||
->setParam('permissions', \array_filter(
|
||||
Database::PERMISSIONS,
|
||||
fn ($perm) => $perm != Database::PERMISSION_CREATE
|
||||
))
|
||||
->setParam('params', [
|
||||
'collection-id' => '{{router.params.collection}}',
|
||||
'database-id' => '{{router.params.databaseId}}',
|
||||
'document-id' => '{{router.params.id}}',
|
||||
]);
|
||||
|
||||
$page = new View(__DIR__ . '/../../views/console/databases/document.phtml');
|
||||
|
||||
$page
|
||||
->setParam('new', true)
|
||||
->setParam('database', $databaseId)
|
||||
->setParam('collection', $collection)
|
||||
->setParam('permissions', $permissions)
|
||||
->setParam('logs', new View())
|
||||
;
|
||||
|
||||
|
|
@ -392,11 +438,45 @@ App::get('/console/storage/bucket')
|
|||
->inject('layout')
|
||||
->action(function (string $id, Response $response, View $layout) {
|
||||
|
||||
$bucketPermissions = new View(__DIR__ . '/../../views/console/comps/permissions-matrix.phtml');
|
||||
$bucketPermissions
|
||||
->setParam('method', 'databases.getBucket')
|
||||
->setParam('events', 'load,databases.updateBucket')
|
||||
->setParam('data', 'project-bucket')
|
||||
->setParam('form', 'bucketPermissions')
|
||||
->setParam('params', [
|
||||
'bucket-id' => '{{router.params.id}}',
|
||||
]);
|
||||
|
||||
$fileCreatePermissions = new View(__DIR__ . '/../../views/console/comps/permissions-matrix.phtml');
|
||||
$fileCreatePermissions
|
||||
->setParam('form', 'fileCreatePermissions')
|
||||
->setParam('permissions', \array_filter(
|
||||
Database::PERMISSIONS,
|
||||
fn ($perm) => $perm != Database::PERMISSION_CREATE
|
||||
));
|
||||
|
||||
$fileUpdatePermissions = new View(__DIR__ . '/../../views/console/comps/permissions-matrix.phtml');
|
||||
$fileUpdatePermissions
|
||||
->setParam('method', 'storage.getFile')
|
||||
->setParam('data', 'file')
|
||||
->setParam('form', 'fileUpdatePermissions')
|
||||
->setParam('permissions', \array_filter(
|
||||
Database::PERMISSIONS,
|
||||
fn ($perm) => $perm != Database::PERMISSION_CREATE
|
||||
))
|
||||
->setParam('params', [
|
||||
'bucket-id' => '{{router.params.id}}',
|
||||
]);
|
||||
|
||||
$page = new View(__DIR__ . '/../../views/console/storage/bucket.phtml');
|
||||
$page
|
||||
->setParam('home', App::getEnv('_APP_HOME', 0))
|
||||
->setParam('fileLimit', App::getEnv('_APP_STORAGE_LIMIT', 0))
|
||||
->setParam('fileLimitHuman', Storage::human(App::getEnv('_APP_STORAGE_LIMIT', 0)))
|
||||
->setParam('bucketPermissions', $bucketPermissions)
|
||||
->setParam('fileCreatePermissions', $fileCreatePermissions)
|
||||
->setParam('fileUpdatePermissions', $fileUpdatePermissions)
|
||||
;
|
||||
|
||||
$layout
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ use Swoole\Runtime;
|
|||
use Swoole\Timer;
|
||||
use Utopia\App;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Logger\Logger;
|
||||
use Utopia\Orchestration\Adapter\DockerCLI;
|
||||
|
|
@ -188,8 +189,9 @@ App::post('/v1/runtimes')
|
|||
$containerId = '';
|
||||
$stdout = '';
|
||||
$stderr = '';
|
||||
$startTime = \time();
|
||||
$endTime = 0;
|
||||
$startTime = DateTime::now();
|
||||
$startTimeUnix = (new \DateTime($startTime))->getTimestamp();
|
||||
$endTimeUnix = 0;
|
||||
$orchestration = $orchestrationPool->get();
|
||||
|
||||
$secret = \bin2hex(\random_bytes(16));
|
||||
|
|
@ -198,8 +200,8 @@ App::post('/v1/runtimes')
|
|||
$activeRuntimes->set($runtimeId, [
|
||||
'id' => $containerId,
|
||||
'name' => $runtimeId,
|
||||
'created' => $startTime,
|
||||
'updated' => $endTime,
|
||||
'created' => $startTimeUnix,
|
||||
'updated' => $endTimeUnix,
|
||||
'status' => 'pending',
|
||||
'key' => $secret,
|
||||
]);
|
||||
|
|
@ -262,7 +264,7 @@ App::post('/v1/runtimes')
|
|||
labels: [
|
||||
'openruntimes-id' => $runtimeId,
|
||||
'openruntimes-type' => 'runtime',
|
||||
'openruntimes-created' => strval($startTime),
|
||||
'openruntimes-created' => strval($startTimeUnix),
|
||||
'openruntimes-runtime' => $runtime,
|
||||
],
|
||||
workdir: $workdir,
|
||||
|
|
@ -319,28 +321,32 @@ App::post('/v1/runtimes')
|
|||
$stdout = 'Build Successful!';
|
||||
}
|
||||
|
||||
$endTime = \time();
|
||||
$endTime = DateTime::now();
|
||||
$endTimeUnix = (new \DateTime($endTime))->getTimestamp();
|
||||
$duration = $endTimeUnix - $startTimeUnix;
|
||||
|
||||
$container = array_merge($container, [
|
||||
'status' => 'ready',
|
||||
'response' => \mb_strcut($stdout, 0, 1000000), // Limit to 1MB
|
||||
'stderr' => \mb_strcut($stderr, 0, 1000000), // Limit to 1MB
|
||||
'startTime' => $startTime,
|
||||
'endTime' => $endTime,
|
||||
'duration' => $endTime - $startTime,
|
||||
'duration' => $duration,
|
||||
]);
|
||||
|
||||
|
||||
if (!$remove) {
|
||||
$activeRuntimes->set($runtimeId, [
|
||||
'id' => $containerId,
|
||||
'name' => $runtimeId,
|
||||
'created' => $startTime,
|
||||
'updated' => $endTime,
|
||||
'status' => 'Up ' . \round($endTime - $startTime, 2) . 's',
|
||||
'created' => $startTimeUnix,
|
||||
'updated' => $endTimeUnix,
|
||||
'status' => 'Up ' . \round($duration, 2) . 's',
|
||||
'key' => $secret,
|
||||
]);
|
||||
}
|
||||
|
||||
Console::success('Build Stage completed in ' . ($endTime - $startTime) . ' seconds');
|
||||
Console::success('Build Stage completed in ' . ($duration) . ' seconds');
|
||||
} catch (Throwable $th) {
|
||||
Console::error('Build failed: ' . $th->getMessage() . $stdout);
|
||||
|
||||
|
|
|
|||
27
app/http.php
27
app/http.php
|
|
@ -10,6 +10,9 @@ use Swoole\Http\Response as SwooleResponse;
|
|||
use Utopia\App;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\ID;
|
||||
use Utopia\Database\Permission;
|
||||
use Utopia\Database\Role;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Audit\Audit;
|
||||
use Utopia\Abuse\Adapters\TimeLimit;
|
||||
|
|
@ -132,7 +135,7 @@ $http->on('start', function (Server $http) use ($payloadSize, $register) {
|
|||
|
||||
foreach ($collection['attributes'] as $attribute) {
|
||||
$attributes[] = new Document([
|
||||
'$id' => $attribute['$id'],
|
||||
'$id' => ID::custom($attribute['$id']),
|
||||
'type' => $attribute['type'],
|
||||
'size' => $attribute['size'],
|
||||
'required' => $attribute['required'],
|
||||
|
|
@ -146,7 +149,7 @@ $http->on('start', function (Server $http) use ($payloadSize, $register) {
|
|||
|
||||
foreach ($collection['indexes'] as $index) {
|
||||
$indexes[] = new Document([
|
||||
'$id' => $index['$id'],
|
||||
'$id' => ID::custom($index['$id']),
|
||||
'type' => $index['type'],
|
||||
'attributes' => $index['attributes'],
|
||||
'lengths' => $index['lengths'],
|
||||
|
|
@ -160,17 +163,21 @@ $http->on('start', function (Server $http) use ($payloadSize, $register) {
|
|||
if ($dbForConsole->getDocument('buckets', 'default')->isEmpty()) {
|
||||
Console::success('[Setup] - Creating default bucket...');
|
||||
$dbForConsole->createDocument('buckets', new Document([
|
||||
'$id' => 'default',
|
||||
'$collection' => 'buckets',
|
||||
'$id' => ID::custom('default'),
|
||||
'$collection' => ID::custom('buckets'),
|
||||
'name' => 'Default',
|
||||
'permission' => 'file',
|
||||
'maximumFileSize' => (int) App::getEnv('_APP_STORAGE_LIMIT', 0), // 10MB
|
||||
'allowedFileExtensions' => [],
|
||||
'enabled' => true,
|
||||
'encryption' => true,
|
||||
'antivirus' => true,
|
||||
'$read' => ['role:all'],
|
||||
'$write' => ['role:all'],
|
||||
'fileSecurity' => true,
|
||||
'$permissions' => [
|
||||
Permission::create(Role::any()),
|
||||
Permission::read(Role::any()),
|
||||
Permission::update(Role::any()),
|
||||
Permission::delete(Role::any()),
|
||||
],
|
||||
'search' => 'buckets Default',
|
||||
]));
|
||||
|
||||
|
|
@ -187,7 +194,7 @@ $http->on('start', function (Server $http) use ($payloadSize, $register) {
|
|||
|
||||
foreach ($files['attributes'] as $attribute) {
|
||||
$attributes[] = new Document([
|
||||
'$id' => $attribute['$id'],
|
||||
'$id' => ID::custom($attribute['$id']),
|
||||
'type' => $attribute['type'],
|
||||
'size' => $attribute['size'],
|
||||
'required' => $attribute['required'],
|
||||
|
|
@ -201,7 +208,7 @@ $http->on('start', function (Server $http) use ($payloadSize, $register) {
|
|||
|
||||
foreach ($files['indexes'] as $index) {
|
||||
$indexes[] = new Document([
|
||||
'$id' => $index['$id'],
|
||||
'$id' => ID::custom($index['$id']),
|
||||
'type' => $index['type'],
|
||||
'attributes' => $index['attributes'],
|
||||
'lengths' => $index['lengths'],
|
||||
|
|
@ -252,7 +259,7 @@ $http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swo
|
|||
|
||||
try {
|
||||
Authorization::cleanRoles();
|
||||
Authorization::setRole('role:all');
|
||||
Authorization::setRole(Role::any()->toString());
|
||||
|
||||
$app->run($request, $response);
|
||||
} catch (\Throwable $th) {
|
||||
|
|
|
|||
74
app/init.php
74
app/init.php
|
|
@ -43,6 +43,7 @@ use Appwrite\OpenSSL\OpenSSL;
|
|||
use Appwrite\Usage\Stats;
|
||||
use Appwrite\Utopia\View;
|
||||
use Utopia\App;
|
||||
use Utopia\Database\ID;
|
||||
use Utopia\Logger\Logger;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Locale\Locale;
|
||||
|
|
@ -63,6 +64,7 @@ use Swoole\Database\PDOPool;
|
|||
use Swoole\Database\RedisConfig;
|
||||
use Swoole\Database\RedisPool;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\DatetimeValidator;
|
||||
use Utopia\Storage\Device;
|
||||
use Utopia\Storage\Storage;
|
||||
use Utopia\Storage\Device\Backblaze;
|
||||
|
|
@ -93,6 +95,7 @@ const APP_VERSION_STABLE = '0.15.3';
|
|||
const APP_DATABASE_ATTRIBUTE_EMAIL = 'email';
|
||||
const APP_DATABASE_ATTRIBUTE_ENUM = 'enum';
|
||||
const APP_DATABASE_ATTRIBUTE_IP = 'ip';
|
||||
const APP_DATABASE_ATTRIBUTE_DATETIME = 'datetime';
|
||||
const APP_DATABASE_ATTRIBUTE_URL = 'url';
|
||||
const APP_DATABASE_ATTRIBUTE_INT_RANGE = 'intRange';
|
||||
const APP_DATABASE_ATTRIBUTE_FLOAT_RANGE = 'floatRange';
|
||||
|
|
@ -269,9 +272,10 @@ Database::addFilter(
|
|||
function (mixed $value, Document $document, Database $database) {
|
||||
return $database
|
||||
->find('attributes', [
|
||||
new Query('collectionInternalId', Query::TYPE_EQUAL, [$document->getInternalId()]),
|
||||
new Query('databaseInternalId', Query::TYPE_EQUAL, [$document->getAttribute('databaseInternalId')])
|
||||
], $database->getAttributeLimit(), 0, []);
|
||||
Query::equal('collectionInternalId', [$document->getInternalId()]),
|
||||
Query::equal('databaseInternalId', [$document->getAttribute('databaseInternalId')]),
|
||||
Query::limit($database->getAttributeLimit()),
|
||||
]);
|
||||
}
|
||||
);
|
||||
|
||||
|
|
@ -283,9 +287,10 @@ Database::addFilter(
|
|||
function (mixed $value, Document $document, Database $database) {
|
||||
return $database
|
||||
->find('indexes', [
|
||||
new Query('collectionInternalId', Query::TYPE_EQUAL, [$document->getInternalId()]),
|
||||
new Query('databaseInternalId', Query::TYPE_EQUAL, [$document->getAttribute('databaseInternalId')])
|
||||
], 64);
|
||||
Query::equal('collectionInternalId', [$document->getInternalId()]),
|
||||
Query::equal('databaseInternalId', [$document->getAttribute('databaseInternalId')]),
|
||||
Query::limit(64),
|
||||
]);
|
||||
}
|
||||
);
|
||||
|
||||
|
|
@ -297,8 +302,9 @@ Database::addFilter(
|
|||
function (mixed $value, Document $document, Database $database) {
|
||||
return $database
|
||||
->find('platforms', [
|
||||
new Query('projectInternalId', Query::TYPE_EQUAL, [$document->getInternalId()])
|
||||
], APP_LIMIT_SUBQUERY);
|
||||
Query::equal('projectInternalId', [$document->getInternalId()]),
|
||||
Query::limit(APP_LIMIT_SUBQUERY),
|
||||
]);
|
||||
}
|
||||
);
|
||||
|
||||
|
|
@ -310,8 +316,9 @@ Database::addFilter(
|
|||
function (mixed $value, Document $document, Database $database) {
|
||||
return $database
|
||||
->find('domains', [
|
||||
new Query('projectInternalId', Query::TYPE_EQUAL, [$document->getInternalId()])
|
||||
], APP_LIMIT_SUBQUERY);
|
||||
Query::equal('projectInternalId', [$document->getInternalId()]),
|
||||
Query::limit(APP_LIMIT_SUBQUERY),
|
||||
]);
|
||||
}
|
||||
);
|
||||
|
||||
|
|
@ -323,8 +330,9 @@ Database::addFilter(
|
|||
function (mixed $value, Document $document, Database $database) {
|
||||
return $database
|
||||
->find('keys', [
|
||||
new Query('projectInternalId', Query::TYPE_EQUAL, [$document->getInternalId()])
|
||||
], APP_LIMIT_SUBQUERY);
|
||||
Query::equal('projectInternalId', [$document->getInternalId()]),
|
||||
Query::limit(APP_LIMIT_SUBQUERY),
|
||||
]);
|
||||
}
|
||||
);
|
||||
|
||||
|
|
@ -336,8 +344,9 @@ Database::addFilter(
|
|||
function (mixed $value, Document $document, Database $database) {
|
||||
return $database
|
||||
->find('webhooks', [
|
||||
new Query('projectInternalId', Query::TYPE_EQUAL, [$document->getInternalId()])
|
||||
], APP_LIMIT_SUBQUERY);
|
||||
Query::equal('projectInternalId', [$document->getInternalId()]),
|
||||
Query::limit(APP_LIMIT_SUBQUERY),
|
||||
]);
|
||||
}
|
||||
);
|
||||
|
||||
|
|
@ -348,8 +357,9 @@ Database::addFilter(
|
|||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return Authorization::skip(fn () => $database->find('sessions', [
|
||||
new Query('userInternalId', Query::TYPE_EQUAL, [$document->getInternalId()])
|
||||
], APP_LIMIT_SUBQUERY));
|
||||
Query::equal('userInternalId', [$document->getInternalId()]),
|
||||
Query::limit(APP_LIMIT_SUBQUERY),
|
||||
]));
|
||||
}
|
||||
);
|
||||
|
||||
|
|
@ -361,8 +371,9 @@ Database::addFilter(
|
|||
function (mixed $value, Document $document, Database $database) {
|
||||
return Authorization::skip(fn() => $database
|
||||
->find('tokens', [
|
||||
new Query('userInternalId', Query::TYPE_EQUAL, [$document->getInternalId()])
|
||||
], APP_LIMIT_SUBQUERY));
|
||||
Query::equal('userInternalId', [$document->getInternalId()]),
|
||||
Query::limit(APP_LIMIT_SUBQUERY),
|
||||
]));
|
||||
}
|
||||
);
|
||||
|
||||
|
|
@ -374,8 +385,9 @@ Database::addFilter(
|
|||
function (mixed $value, Document $document, Database $database) {
|
||||
return Authorization::skip(fn() => $database
|
||||
->find('memberships', [
|
||||
new Query('userInternalId', Query::TYPE_EQUAL, [$document->getInternalId()])
|
||||
], APP_LIMIT_SUBQUERY));
|
||||
Query::equal('userInternalId', [$document->getInternalId()]),
|
||||
Query::limit(APP_LIMIT_SUBQUERY),
|
||||
]));
|
||||
}
|
||||
);
|
||||
|
||||
|
|
@ -412,6 +424,10 @@ Structure::addFormat(APP_DATABASE_ATTRIBUTE_EMAIL, function () {
|
|||
return new Email();
|
||||
}, Database::VAR_STRING);
|
||||
|
||||
Structure::addFormat(APP_DATABASE_ATTRIBUTE_DATETIME, function () {
|
||||
return new DatetimeValidator();
|
||||
}, Database::VAR_DATETIME);
|
||||
|
||||
Structure::addFormat(APP_DATABASE_ATTRIBUTE_ENUM, function ($attribute) {
|
||||
$elements = $attribute['formatOptions']['elements'];
|
||||
return new WhiteList($elements, true);
|
||||
|
|
@ -713,7 +729,7 @@ App::setResource('usage', function ($register) {
|
|||
|
||||
App::setResource('clients', function ($request, $console, $project) {
|
||||
$console->setAttribute('platforms', [ // Always allow current host
|
||||
'$collection' => 'platforms',
|
||||
'$collection' => ID::custom('platforms'),
|
||||
'name' => 'Current Host',
|
||||
'type' => 'web',
|
||||
'hostname' => $request->getHostname(),
|
||||
|
|
@ -789,7 +805,7 @@ App::setResource('user', function ($mode, $project, $console, $request, $respons
|
|||
|
||||
if (APP_MODE_ADMIN !== $mode) {
|
||||
if ($project->isEmpty()) {
|
||||
$user = new Document(['$id' => '', '$collection' => 'users']);
|
||||
$user = new Document(['$id' => ID::custom(''), '$collection' => 'users']);
|
||||
} else {
|
||||
$user = $dbForProject->getDocument('users', Auth::$unique);
|
||||
}
|
||||
|
|
@ -801,14 +817,14 @@ App::setResource('user', function ($mode, $project, $console, $request, $respons
|
|||
$user->isEmpty() // Check a document has been found in the DB
|
||||
|| !Auth::sessionVerify($user->getAttribute('sessions', []), Auth::$secret)
|
||||
) { // Validate user has valid login token
|
||||
$user = new Document(['$id' => '', '$collection' => 'users']);
|
||||
$user = new Document(['$id' => ID::custom(''), '$collection' => 'users']);
|
||||
}
|
||||
|
||||
if (APP_MODE_ADMIN === $mode) {
|
||||
if ($user->find('teamId', $project->getAttribute('teamId'), 'memberships')) {
|
||||
Authorization::setDefaultStatus(false); // Cancel security segmentation for admin users.
|
||||
} else {
|
||||
$user = new Document(['$id' => '', '$collection' => 'users']);
|
||||
$user = new Document(['$id' => ID::custom(''), '$collection' => 'users']);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -831,7 +847,7 @@ App::setResource('user', function ($mode, $project, $console, $request, $respons
|
|||
}
|
||||
|
||||
if (empty($user->find('$id', $jwtSessionId, 'sessions'))) { // Match JWT to active token
|
||||
$user = new Document(['$id' => '', '$collection' => 'users']);
|
||||
$user = new Document(['$id' => ID::custom(''), '$collection' => 'users']);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -856,10 +872,10 @@ App::setResource('project', function ($dbForConsole, $request, $console) {
|
|||
|
||||
App::setResource('console', function () {
|
||||
return new Document([
|
||||
'$id' => 'console',
|
||||
'$internalId' => 'console',
|
||||
'$id' => ID::custom('console'),
|
||||
'$internalId' => ID::custom('console'),
|
||||
'name' => 'Appwrite',
|
||||
'$collection' => 'projects',
|
||||
'$collection' => ID::custom('projects'),
|
||||
'description' => 'Appwrite core engine',
|
||||
'logo' => '',
|
||||
'teamId' => -1,
|
||||
|
|
@ -867,7 +883,7 @@ App::setResource('console', function () {
|
|||
'keys' => [],
|
||||
'platforms' => [
|
||||
[
|
||||
'$collection' => 'platforms',
|
||||
'$collection' => ID::custom('platforms'),
|
||||
'name' => 'Localhost',
|
||||
'type' => 'web',
|
||||
'hostname' => 'localhost',
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
<?php
|
||||
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Messaging\Adapter\Realtime;
|
||||
use Appwrite\Network\Validator\Origin;
|
||||
use Appwrite\Utopia\Response;
|
||||
|
|
@ -14,8 +13,11 @@ use Utopia\Abuse\Abuse;
|
|||
use Utopia\Abuse\Adapters\TimeLimit;
|
||||
use Utopia\App;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Database\ID;
|
||||
use Utopia\Database\Role;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Cache\Adapter\Redis as RedisCache;
|
||||
use Utopia\Cache\Cache;
|
||||
use Utopia\Database\Adapter\MariaDB;
|
||||
|
|
@ -134,7 +136,7 @@ function getDatabase(Registry &$register, string $namespace)
|
|||
|
||||
$server->onStart(function () use ($stats, $register, $containerId, &$statsDocument, $logError) {
|
||||
sleep(5); // wait for the initial database schema to be ready
|
||||
Console::success('Server started succefully');
|
||||
Console::success('Server started successfully');
|
||||
|
||||
/**
|
||||
* Create document for this worker to share stats across Containers.
|
||||
|
|
@ -146,12 +148,11 @@ $server->onStart(function () use ($stats, $register, $containerId, &$statsDocume
|
|||
try {
|
||||
$attempts++;
|
||||
$document = new Document([
|
||||
'$id' => $database->getId(),
|
||||
'$collection' => 'realtime',
|
||||
'$read' => [],
|
||||
'$write' => [],
|
||||
'$id' => ID::unique(),
|
||||
'$collection' => ID::custom('realtime'),
|
||||
'$permissions' => [],
|
||||
'container' => $containerId,
|
||||
'timestamp' => time(),
|
||||
'timestamp' => DateTime::now(),
|
||||
'value' => '{}'
|
||||
]);
|
||||
|
||||
|
|
@ -181,7 +182,7 @@ $server->onStart(function () use ($stats, $register, $containerId, &$statsDocume
|
|||
[$database, $returnDatabase] = getDatabase($register, '_console');
|
||||
|
||||
$statsDocument
|
||||
->setAttribute('timestamp', time())
|
||||
->setAttribute('timestamp', DateTime::now())
|
||||
->setAttribute('value', json_encode($payload));
|
||||
|
||||
Authorization::skip(fn () => $database->updateDocument('realtime', $statsDocument->getId(), $statsDocument));
|
||||
|
|
@ -203,13 +204,13 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
|
|||
/**
|
||||
* Sending current connections to project channels on the console project every 5 seconds.
|
||||
*/
|
||||
if ($realtime->hasSubscriber('console', 'role:member', 'project')) {
|
||||
if ($realtime->hasSubscriber('console', Role::users()->toString(), 'project')) {
|
||||
[$database, $returnDatabase] = getDatabase($register, '_console');
|
||||
|
||||
$payload = [];
|
||||
|
||||
$list = Authorization::skip(fn () => $database->find('realtime', [
|
||||
new Query('timestamp', Query::TYPE_GREATER, [(time() - 15)])
|
||||
Query::greaterThan('timestamp', DateTime::addSeconds(new \DateTime(), -15)),
|
||||
]));
|
||||
|
||||
/**
|
||||
|
|
@ -236,7 +237,7 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
|
|||
'data' => [
|
||||
'events' => ['stats.connections'],
|
||||
'channels' => ['project'],
|
||||
'timestamp' => time(),
|
||||
'timestamp' => DateTime::now(),
|
||||
'payload' => [
|
||||
$projectId => $payload[$projectId]
|
||||
]
|
||||
|
|
@ -254,16 +255,16 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
|
|||
/**
|
||||
* Sending test message for SDK E2E tests every 5 seconds.
|
||||
*/
|
||||
if ($realtime->hasSubscriber('console', 'role:guest', 'tests')) {
|
||||
if ($realtime->hasSubscriber('console', Role::guests()->toString(), 'tests')) {
|
||||
$payload = ['response' => 'WS:/v1/realtime:passed'];
|
||||
|
||||
$event = [
|
||||
'project' => 'console',
|
||||
'roles' => ['role:guest'],
|
||||
'roles' => [Role::guests()->toString()],
|
||||
'data' => [
|
||||
'events' => ['test.event'],
|
||||
'channels' => ['tests'],
|
||||
'timestamp' => time(),
|
||||
'timestamp' => DateTime::now(),
|
||||
'payload' => $payload
|
||||
]
|
||||
];
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ use Utopia\Cache\Cache;
|
|||
use Utopia\CLI\Console;
|
||||
use Utopia\Database\Adapter\MariaDB;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Cache\Adapter\Redis as RedisCache;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Query;
|
||||
|
|
@ -57,7 +58,7 @@ $cli
|
|||
{
|
||||
(new Delete())
|
||||
->setType(DELETE_TYPE_EXECUTIONS)
|
||||
->setTimestamp(time() - $interval)
|
||||
->setDatetime(DateTime::addSeconds(new \DateTime(), -1 * $interval))
|
||||
->trigger();
|
||||
}
|
||||
|
||||
|
|
@ -65,7 +66,7 @@ $cli
|
|||
{
|
||||
(new Delete())
|
||||
->setType(DELETE_TYPE_ABUSE)
|
||||
->setTimestamp(time() - $interval)
|
||||
->setDatetime(DateTime::addSeconds(new \DateTime(), -1 * $interval))
|
||||
->trigger();
|
||||
}
|
||||
|
||||
|
|
@ -73,7 +74,7 @@ $cli
|
|||
{
|
||||
(new Delete())
|
||||
->setType(DELETE_TYPE_AUDIT)
|
||||
->setTimestamp(time() - $interval)
|
||||
->setDatetime(DateTime::addSeconds(new \DateTime(), -1 * $interval))
|
||||
->trigger();
|
||||
}
|
||||
|
||||
|
|
@ -81,8 +82,8 @@ $cli
|
|||
{
|
||||
(new Delete())
|
||||
->setType(DELETE_TYPE_USAGE)
|
||||
->setTimestamp1d(time() - $interval1d)
|
||||
->setTimestamp30m(time() - $interval30m)
|
||||
->setDateTime1d(DateTime::addSeconds(new \DateTime(), -1 * $interval1d))
|
||||
->setDateTime30m(DateTime::addSeconds(new \DateTime(), -1 * $interval30m))
|
||||
->trigger();
|
||||
}
|
||||
|
||||
|
|
@ -90,7 +91,7 @@ $cli
|
|||
{
|
||||
(new Delete())
|
||||
->setType(DELETE_TYPE_REALTIME)
|
||||
->setTimestamp(time() - 60)
|
||||
->setDatetime(DateTime::addSeconds(new \DateTime(), -60))
|
||||
->trigger();
|
||||
}
|
||||
|
||||
|
|
@ -98,16 +99,17 @@ $cli
|
|||
{
|
||||
(new Delete())
|
||||
->setType(DELETE_TYPE_SESSIONS)
|
||||
->setTimestamp(time() - Auth::TOKEN_EXPIRATION_LOGIN_LONG)
|
||||
->setDatetime(DateTime::addSeconds(new \DateTime(), -1 * Auth::TOKEN_EXPIRATION_LOGIN_LONG))
|
||||
->trigger();
|
||||
}
|
||||
|
||||
function renewCertificates($dbForConsole)
|
||||
{
|
||||
$time = date('d-m-Y H:i:s', time());
|
||||
$time = DateTime::now();
|
||||
|
||||
$certificates = $dbForConsole->find('certificates', [
|
||||
new Query('attempts', Query::TYPE_LESSEREQUAL, [5]), // Maximum 5 attempts
|
||||
new Query('renewDate', Query::TYPE_LESSEREQUAL, [\time()]) // includes 60 days cooldown (we have 30 days to renew)
|
||||
Query::lessThanEqual('attempts', 5), // Maximum 5 attempts
|
||||
Query::lessThanEqual('renewDate', $time) // includes 60 days cooldown (we have 30 days to renew)
|
||||
], 200); // Limit 200 comes from LetsEncrypt (300 orders per 3 hours, keeping some for new domains)
|
||||
|
||||
|
||||
|
|
@ -132,7 +134,7 @@ $cli
|
|||
|
||||
(new Delete())
|
||||
->setType(DELETE_TYPE_CACHE_BY_TIMESTAMP)
|
||||
->setTimestamp(time() - $interval)
|
||||
->setDatetime(DateTime::addSeconds(new \DateTime(), -1 * $interval))
|
||||
->trigger();
|
||||
}
|
||||
|
||||
|
|
@ -148,7 +150,8 @@ $cli
|
|||
Console::loop(function () use ($interval, $executionLogsRetention, $abuseLogsRetention, $auditLogRetention, $usageStatsRetention30m, $usageStatsRetention1d, $cacheRetention) {
|
||||
$database = getConsoleDB();
|
||||
|
||||
$time = date('d-m-Y H:i:s', time());
|
||||
$time = DateTime::now();
|
||||
|
||||
Console::info("[{$time}] Notifying workers with maintenance tasks every {$interval} seconds");
|
||||
notifyDeleteExecutionLogs($executionLogsRetention);
|
||||
notifyDeleteAbuseLogs($abuseLogsRetention);
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ use Utopia\Cache\Cache;
|
|||
use Utopia\Cache\Adapter\Redis as RedisCache;
|
||||
use Utopia\Database\Adapter\MariaDB;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
|
|
@ -70,7 +71,7 @@ $cli
|
|||
}
|
||||
|
||||
$sum = \count($projects);
|
||||
$projects = $consoleDB->find('projects', limit: $limit, offset: $offset);
|
||||
$projects = $consoleDB->find('projects', [Query::limit($limit), Query::offset($offset)]);
|
||||
|
||||
$offset = $offset + $limit;
|
||||
$count = $count + $sum;
|
||||
|
|
|
|||
117
app/views/console/comps/permissions-matrix.phtml
Normal file
117
app/views/console/comps/permissions-matrix.phtml
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
<?php
|
||||
|
||||
use Utopia\Database\Database;
|
||||
|
||||
$method = $this->getParam('method', '');
|
||||
$params = $this->getParam('params', []);
|
||||
$events = $this->getParam('events', '');
|
||||
$permissions = $this->getParam('permissions', Database::PERMISSIONS);
|
||||
$data = $this->getParam('data', '');
|
||||
$form = $this->getParam('form', 'form');
|
||||
|
||||
$escapedPermissions = \array_map(function ($perm) {
|
||||
// Alpine won't bind to a parameter named delete
|
||||
if ($perm == 'delete') {
|
||||
return 'xdelete';
|
||||
}
|
||||
return $perm;
|
||||
}, $permissions);
|
||||
|
||||
?>
|
||||
<div
|
||||
x-data="permissionsMatrix"
|
||||
class="permissions-matrix margin-bottom-large"
|
||||
data-scope="sdk"
|
||||
<?php if (!empty($method)): ?>
|
||||
data-method="<?php echo $method; ?>"
|
||||
<?php endif; ?>
|
||||
<?php foreach ($params as $key => $value): ?>
|
||||
data-param-<?php echo $key; ?>="<?php echo $value; ?>"
|
||||
<?php endforeach; ?>
|
||||
<?php if (!empty($events)): ?>
|
||||
data-events="<?php echo $events; ?>"
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($data)): ?>
|
||||
data-name="<?php echo $data; ?>"
|
||||
<?php endif; ?>
|
||||
@reset.window="permissions = rawPermissions = []">
|
||||
|
||||
<input
|
||||
type="hidden"
|
||||
name="permissions"
|
||||
data-cast-from="csv"
|
||||
data-cast-to="array"
|
||||
<?php if (!empty(($data))): ?>
|
||||
data-ls-bind="{{<?php echo $data ?>.$permissions}}"
|
||||
<?php endif; ?>
|
||||
:value="rawPermissions"/>
|
||||
|
||||
<table data-ls-attrs="x-init=load({{<?php if (!empty($data)) echo $data . '.$permissions' ?>}})">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Role</th>
|
||||
<?php foreach ($permissions as $permission): ?>
|
||||
<th><?php echo \ucfirst($permission); ?></th>
|
||||
<?php endforeach; ?>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<template x-for="(permission, index) in permissions">
|
||||
<tr>
|
||||
<td>
|
||||
<p x-text="permission.role"></p>
|
||||
</td>
|
||||
<?php foreach ($escapedPermissions as $permission): ?>
|
||||
<td>
|
||||
<input
|
||||
type="checkbox"
|
||||
name="<?php echo $permission ?>"
|
||||
x-model="permission.<?php echo $permission; ?>"
|
||||
@click="updatePermission(index)"/>
|
||||
</td>
|
||||
<?php endforeach; ?>
|
||||
<td>
|
||||
<span class="action" @click="removePermission(index)">
|
||||
<i class="icon-trash"></i>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
<tr x-data="permissionsRow"
|
||||
@addrow.window="addPermission('<?php echo $form; ?>',role,{<?php echo \implode(',', $escapedPermissions) ?>})">
|
||||
<td>
|
||||
<datalist id="types">
|
||||
<option value="user:">
|
||||
<option value="team:">
|
||||
<option value="users">
|
||||
<option value="guests">
|
||||
<option value="any">
|
||||
</datalist>
|
||||
|
||||
<input
|
||||
required
|
||||
id="<?php echo $form; ?>"
|
||||
name="<?php echo $form; ?>"
|
||||
form="<?php echo $form ?>"
|
||||
list="types"
|
||||
type="text"
|
||||
x-model="role" />
|
||||
</td>
|
||||
<?php foreach ($escapedPermissions as $permission): ?>
|
||||
<td>
|
||||
<input type="checkbox" name="<?php echo $permission ?>" x-model="<?php echo $permission; ?>"/>
|
||||
</td>
|
||||
<?php endforeach; ?>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td colspan="<?php \count($permissions) + 2 ?>">
|
||||
<button type="button" class="btn btn-primary margin-top-small" @click="$dispatch('addrow')">Add</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
<?php
|
||||
|
||||
$logs = $this->getParam('logs', null);
|
||||
$permissions = $this->getParam('permissions', null);
|
||||
|
||||
?>
|
||||
<div
|
||||
|
|
@ -317,6 +318,9 @@ $logs = $this->getParam('logs', null);
|
|||
<li>
|
||||
<div class="link new-attribute-boolean"><i class="avatar icon-boolean"></i> New Boolean Attribute</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="link new-attribute-datetime"><i class="avatar icon-string"></i> New DateTime Attribute</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="link new-attribute-url"><i class="avatar icon-link"></i> New URL Attribute</div>
|
||||
</li>
|
||||
|
|
@ -486,8 +490,8 @@ $logs = $this->getParam('logs', null);
|
|||
<div class="box margin-bottom-small">
|
||||
<div class="margin-start-negative-small margin-end-negative-small margin-top-negative-small margin-bottom-negative-small">
|
||||
<div class="chart background-image-no border-no margin-bottom-no">
|
||||
<input
|
||||
type="hidden"
|
||||
<input
|
||||
type="hidden"
|
||||
data-ls-bind="{{usage}}"
|
||||
data-forms-chart="Created=documentsCreate,Read=documentsRead,Updated=documentsUpdate,Deleted=documentsDelete"
|
||||
data-show-y-axis="true"
|
||||
|
|
@ -510,6 +514,8 @@ $logs = $this->getParam('logs', null);
|
|||
|
||||
<div class="row responsive margin-top-negative">
|
||||
<div class="col span-8 margin-bottom">
|
||||
<form id="<?php echo $permissions->getParam('form', 'permissions') ?>"></form>
|
||||
|
||||
<form
|
||||
data-analytics
|
||||
data-analytics-activity
|
||||
|
|
@ -528,8 +534,6 @@ $logs = $this->getParam('logs', null);
|
|||
data-failure-param-alert-text="Failed to update collection"
|
||||
data-failure-param-alert-classname="error">
|
||||
|
||||
<label> </label>
|
||||
|
||||
<div class="box">
|
||||
<label for="collection-name">Name</label>
|
||||
<input name="name" id="collection-name" type="text" autocomplete="off" data-ls-bind="{{project-collection.name}}" data-forms-text-direction required placeholder="Collection Name" maxlength="128" />
|
||||
|
|
@ -538,36 +542,25 @@ $logs = $this->getParam('logs', null);
|
|||
<input name="enabled" type="hidden" data-forms-switch data-cast-to="boolean" data-ls-bind="{{project-collection.enabled}}" /> Enabled <span class="tooltip" data-tooltip="Mark whether collection is enabled"><i class="icon-info-circled"></i></span>
|
||||
</div>
|
||||
|
||||
<hr class="margin-top-small" />
|
||||
|
||||
<label class="margin-bottom-small">Permissions</label>
|
||||
|
||||
<p class="text-fade text-size-small">Choose the permissions model for this collection.</p>
|
||||
<p class="text-fade text-size-small">Configure the permissions for this collection.</p>
|
||||
|
||||
<hr class="margin-top-small" />
|
||||
|
||||
<div class="row">
|
||||
<div class="col span-1"><input name="permission" value="collection" type="radio" class="margin-top-tiny" data-ls-bind="{{project-collection.permission}}" /></div>
|
||||
<div class="col span-11">
|
||||
<b>Collection Level</b>
|
||||
<p class="text-fade margin-top-tiny">With Collection Level permissions, you assign permissions only once in the collection.</p>
|
||||
<p class="text-fade margin-top-tiny">In this permission level, permissions assigned to collection takes the precedence and documents permissions are ignored.</p>
|
||||
<div data-ls-if="{{project-collection.permission}} === 'collection'">
|
||||
<label for="collection-read">Read Access <span class="text-size-small">(<a data-ls-attrs="href={{env.HOME}}/docs/permissions" target="_blank" rel="noopener">Learn more</a>)</span></label>
|
||||
<input type="hidden" id="collection-read" name="read" data-forms-tags data-cast-to="json" data-ls-bind="{{project-collection.$read}}" placeholder="User ID, Team ID or Role" />
|
||||
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Add 'role:all' for wildcard access</div>
|
||||
<?php echo $permissions->render(); ?>
|
||||
|
||||
<label for="collection-write">Write Access <span class="text-size-small">(<a data-ls-attrs="href={{env.HOME}}/docs/permissions" target="_blank" rel="noopener">Learn more</a>)</label>
|
||||
<input type="hidden" id="collection-write" name="write" data-forms-tags data-cast-to="json" data-ls-bind="{{project-collection.$write}}" placeholder="User ID, Team ID or Role" />
|
||||
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Add 'role:all' for wildcard access</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="margin-top-no" />
|
||||
|
||||
<label class="margin-bottom-small">Document Security</label>
|
||||
|
||||
<div class="row">
|
||||
<div class="col span-1"><input name="permission" value="document" type="radio" class="margin-top-no" data-ls-bind="{{project-collection.permission}}" /></div>
|
||||
<div class="col span-1"><input name="documentSecurity" value="false" type="checkbox" class="margin-top-no" data-ls-bind="{{project-collection.documentSecurity}}" /></div>
|
||||
<div class="col span-11">
|
||||
<b>Document Level</b>
|
||||
<p class="text-fade margin-top-tiny">With Document Level permissions, you have granular access control over every document. Users will only be able to access documents for which they have explicit permissions.</p>
|
||||
<p class="text-fade margin-top-tiny">In this permission level, document permissions take precedence and collection permissions are ignored.</p>
|
||||
<b>Enabled</b>
|
||||
<p class="text-fade margin-top-tiny">With Document Security enabled, users will be able to access documents for which they have been granted <b>either</b> Document or Collection permissions.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -589,9 +582,9 @@ $logs = $this->getParam('logs', null);
|
|||
</div>
|
||||
|
||||
<ul class="margin-bottom-large text-fade text-size-small">
|
||||
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i>
|
||||
<button data-ls-ui-trigger="open-json"
|
||||
class="link text-size-small"
|
||||
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i>
|
||||
<button data-ls-ui-trigger="open-json"
|
||||
class="link text-size-small"
|
||||
data-analytics
|
||||
data-analytics-event="click"
|
||||
data-analytics-category="console"
|
||||
|
|
@ -599,8 +592,8 @@ $logs = $this->getParam('logs', null);
|
|||
View as JSON
|
||||
</button>
|
||||
</li>
|
||||
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> Last Updated: <span data-ls-bind="{{project-collection.$updatedAt|dateText}}"></span></li>
|
||||
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> Created: <span data-ls-bind="{{project-collection.$createdAt|dateText}}"></span></li>
|
||||
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> Last Updated: <span data-ls-bind="{{project-collection.$updatedAt|date}}"></span></li>
|
||||
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> Created: <span data-ls-bind="{{project-collection.$createdAt|date}}"></span></li>
|
||||
</ul>
|
||||
|
||||
<form
|
||||
|
|
@ -688,6 +681,60 @@ $logs = $this->getParam('logs', null);
|
|||
</form>
|
||||
</div>
|
||||
|
||||
<div data-ui-modal class="modal box close sticky-footer" data-button-alias=".new-attribute-datetime">
|
||||
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
|
||||
|
||||
<h1>Add DateTime Attribute</h1>
|
||||
|
||||
<form
|
||||
id="add-datetime-attribute"
|
||||
data-analytics
|
||||
data-analytics-activity
|
||||
data-analytics-event="submit"
|
||||
data-analytics-category="console"
|
||||
data-analytics-label="Create Collection Attribute (datetime)"
|
||||
data-service="databases.createDatetimeAttribute"
|
||||
data-scope="sdk"
|
||||
data-event="submit"
|
||||
data-success="alert,trigger,reset"
|
||||
data-success-param-alert-text="Created new attribute successfully"
|
||||
data-success-param-trigger-events="databases.createAttribute"
|
||||
data-failure="alert"
|
||||
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, size: null }">
|
||||
|
||||
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
|
||||
<input type="hidden" name="collectionId" data-ls-bind="{{router.params.id}}" />
|
||||
<input type="hidden" name="databaseId" data-ls-bind="{{router.params.databaseId}}" />
|
||||
|
||||
<label for="string-key">Attribute ID</label>
|
||||
<input id="string-key" type="text" class="full-width" name="key" required autocomplete="off" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9._-]{0,35}$" />
|
||||
<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>
|
||||
|
||||
<div class="margin-bottom">
|
||||
<input x-model="required" name="required" class="button switch" type="checkbox" /> Required <span class="tooltip" data-tooltip="Mark whether this is a required attribute"><i class="icon-info-circled"></i></span>
|
||||
</div>
|
||||
|
||||
<div class="margin-bottom">
|
||||
<input x-model="array" name="array" class="button switch" type="checkbox" /> Array <span class="tooltip" data-tooltip="Mark whether this attribute should act as an array"><i class="icon-info-circled"></i></span>
|
||||
</div>
|
||||
|
||||
<label for="xdefault">Default Value</label>
|
||||
<template x-if="!(array || required)">
|
||||
<input name="xdefault" type="datetime-local" class="margin-bottom-large">
|
||||
</template>
|
||||
<template x-if="(array || required)">
|
||||
<input name="xdefault" type="datetime-local" class="margin-bottom-large" disabled value="">
|
||||
</template>
|
||||
|
||||
<footer>
|
||||
<button type="submit">Create</button> <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
|
||||
</footer>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div data-ui-modal class="modal box close sticky-footer" data-button-alias=".new-attribute-integer">
|
||||
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
|
||||
|
||||
|
|
|
|||
|
|
@ -131,9 +131,8 @@
|
|||
<label for="collection-name">Name</label>
|
||||
<input type="text" class="full-width" id="collection-name" name="name" required autocomplete="off" maxlength="128" />
|
||||
|
||||
<input type="hidden" id="collection-permission" name="permission" required value="collection" />
|
||||
<input type="hidden" id="collection-read" name="read" required data-cast-to="json" value="<?php echo htmlentities(json_encode([])); ?>" />
|
||||
<input type="hidden" id="collection-write" name="write" required data-cast-to="json" value="<?php echo htmlentities(json_encode([])); ?>" />
|
||||
<input type="hidden" id="collection-permissions" name="permissions" required data-cast-to="json" value="<?php echo htmlentities(json_encode([])); ?>" />
|
||||
<input type="hidden" id="collection-documentSecurity" name="documentSecurity" required data-cast-to="boolean" value="false" />
|
||||
|
||||
<hr />
|
||||
|
||||
|
|
@ -292,8 +291,8 @@
|
|||
|
||||
<ul class="margin-bottom-large text-fade text-size-small">
|
||||
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> <button data-ls-ui-trigger="open-json" class="link text-size-small">View as JSON</button></li>
|
||||
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> Last Updated: <span data-ls-bind="{{project-database.$updatedAt|dateText}}"></span></li>
|
||||
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> Created: <span data-ls-bind="{{project-database.$createdAt|dateText}}"></span></li>
|
||||
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> Last Updated: <span data-ls-bind="{{project-database.$updatedAt|date}}"></span></li>
|
||||
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> Created: <span data-ls-bind="{{project-database.$createdAt|date}}"></span></li>
|
||||
</ul>
|
||||
|
||||
<form
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
$new = $this->getParam('new', false);
|
||||
$logs = $this->getParam('logs', null);
|
||||
$permissions = $this->getParam('permissions', null);
|
||||
|
||||
?>
|
||||
<div
|
||||
|
|
@ -52,6 +53,8 @@ $logs = $this->getParam('logs', null);
|
|||
|
||||
<div class="row responsive">
|
||||
<div class="col span-8 margin-bottom">
|
||||
<form id="<?php echo $permissions->getParam('form', 'permissions') ?>"></form>
|
||||
|
||||
<form
|
||||
data-analytics
|
||||
data-analytics-activity
|
||||
|
|
@ -140,6 +143,16 @@ $logs = $this->getParam('logs', null);
|
|||
:name="attr.key"
|
||||
:checked="doc[attr.key]" />
|
||||
</template>
|
||||
<template x-if="attr.type === 'datetime'">
|
||||
<input
|
||||
type="datetime-local"
|
||||
step=".001"
|
||||
:placeholder="attr.default"
|
||||
:name="attr.key"
|
||||
:required="attr.required"
|
||||
x-model="doc[attr.key]"
|
||||
data-cast-to="string" />
|
||||
</template>
|
||||
<template x-if="attr.type === 'string' && !attr.format">
|
||||
<textarea
|
||||
data-forms-text-resize
|
||||
|
|
@ -240,6 +253,16 @@ $logs = $this->getParam('logs', null);
|
|||
:value="attr.key"
|
||||
:checked="doc[attr.key][index]" />
|
||||
</template>
|
||||
<template x-if="attr.type === 'datetime'">
|
||||
<input
|
||||
type="datetime-local"
|
||||
step=".001"
|
||||
:placeholder="attr.default"
|
||||
:name="attr.key"
|
||||
:required="attr.required"
|
||||
x-model="doc[attr.key][index]"
|
||||
data-cast-to="string" />
|
||||
</template>
|
||||
<template x-if="attr.type === 'string' && !attr.format">
|
||||
<textarea
|
||||
data-forms-text-resize
|
||||
|
|
@ -313,19 +336,13 @@ $logs = $this->getParam('logs', null);
|
|||
</ul>
|
||||
</fieldset>
|
||||
|
||||
<div class="toggle margin-bottom" data-ls-ui-open data-button-aria="Open Permissions">
|
||||
<div class="toggle margin-bottom" data-ls-if="{{project-collection.documentSecurity}}" data-ls-ui-open data-button-aria="Open Permissions">
|
||||
<i class="icon-plus pull-end margin-top-tiny"></i>
|
||||
<i class="icon-minus pull-end margin-top-tiny"></i>
|
||||
|
||||
<h3 class="margin-bottom-large">Permissions</h3>
|
||||
|
||||
<label for="collection-read">Read Access <span class="text-size-small">(<a data-ls-attrs="href={{env.HOME}}/docs/permissions" target="_blank" rel="noopener">Learn more</a>)</span></label>
|
||||
<input type="hidden" id="collection-read" name="read" data-forms-tags data-cast-to="json" data-ls-bind="{{project-document.$read}}" placeholder="User ID, Team ID or Role" />
|
||||
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Add 'role:all' for wildcard access</div>
|
||||
|
||||
<label for="collection-write">Write Access <span class="text-size-small">(<a data-ls-attrs="href={{env.HOME}}/docs/permissions" target="_blank" rel="noopener">Learn more</a>)</label>
|
||||
<input type="hidden" id="collection-write" name="write" data-forms-tags data-cast-to="json" data-ls-bind="{{project-document.$write}}" placeholder="User ID, Team ID or Role" />
|
||||
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Add 'role:all' for wildcard access</div>
|
||||
<?php echo $permissions->render() ?>
|
||||
</div>
|
||||
|
||||
<button data-ls-if="({{project-document.$id}})">Update</button>
|
||||
|
|
@ -364,8 +381,8 @@ $logs = $this->getParam('logs', null);
|
|||
View as JSON
|
||||
</button>
|
||||
</li>
|
||||
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> Last Updated: <span data-ls-bind="{{project-document.$updatedAt|dateText}}"></span></li>
|
||||
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> Created: <span data-ls-bind="{{project-document.$createdAt|dateText}}"></span></li>
|
||||
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> Last Updated: <span data-ls-bind="{{project-document.$updatedAt|date}}"></span></li>
|
||||
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> Created: <span data-ls-bind="{{project-document.$createdAt|date}}"></span></li>
|
||||
</ul>
|
||||
|
||||
<div data-ls-if="({{project-document.$id}})">
|
||||
|
|
|
|||
|
|
@ -260,8 +260,8 @@ sort($patterns);
|
|||
View as JSON
|
||||
</button>
|
||||
</li>
|
||||
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> Last Updated: <span data-ls-bind="{{project-function.$updatedAt|dateText}}"></span></li>
|
||||
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> Created: <span data-ls-bind="{{project-function.$createdAt|dateText}}"></span></li>
|
||||
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> Last Updated: <span data-ls-bind="{{project-function.$updatedAt|date}}"></span></li>
|
||||
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> Created: <span data-ls-bind="{{project-function.$createdAt|date}}"></span></li>
|
||||
</ul>
|
||||
|
||||
<form name="functions.delete" class="margin-bottom"
|
||||
|
|
@ -553,7 +553,7 @@ sort($patterns);
|
|||
|
||||
<label for="execute">Execute Access <span class="tooltip small" data-tooltip="Choose who can execute this function using the client API."><i class="icon-info-circled"></i></span> <span class="text-size-small">(<a data-ls-attrs="href={{env.HOME}}/docs/permissions" target="_blank" rel="noopener">Learn more</a>)</span></label>
|
||||
<input type="hidden" id="execute" name="execute" data-forms-tags data-cast-to="json" data-ls-bind="{{project-function.execute}}" placeholder="User ID, Team ID or Role" />
|
||||
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Add 'role:all' for wildcard access</div>
|
||||
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Add 'any' for wildcard access</div>
|
||||
|
||||
<label for="timeout">Timeout (seconds) <span class="tooltip small" data-tooltip="Limit the execution time of your function."><i class="icon-info-circled"></i></span></label>
|
||||
<input name="timeout" id="function-timeout" type="number" autocomplete="off" data-ls-bind="{{project-function.timeout}}" min="1" max="<?php echo $this->escape($timeout); ?>" data-cast-to="integer" />
|
||||
|
|
@ -635,8 +635,8 @@ sort($patterns);
|
|||
View as JSON
|
||||
</button>
|
||||
</li>
|
||||
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> Last Updated: <span data-ls-bind="{{project-function.$updatedAt|dateText}}"></span></li>
|
||||
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> Created: <span data-ls-bind="{{project-function.$createdAt|dateText}}"></span></li>
|
||||
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> Last Updated: <span data-ls-bind="{{project-function.$updatedAt|date}}"></span></li>
|
||||
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> Created: <span data-ls-bind="{{project-function.$createdAt|date}}"></span></li>
|
||||
</ul>
|
||||
|
||||
<form name="functions.delete" class="margin-bottom"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,8 @@
|
|||
<?php
|
||||
|
||||
use Utopia\Database\Permission;
|
||||
use Utopia\Database\Role;
|
||||
|
||||
$services = $this->getParam('services', []);
|
||||
$customDomainsEnabled = $this->getParam('customDomainsEnabled', false);
|
||||
$customDomainsTarget = $this->getParam('customDomainsTarget', false);
|
||||
|
|
@ -57,24 +61,11 @@ $smtpEnabled = $this->getParam('smtpEnabled', false);
|
|||
<label for="logo">Project Logo</label>
|
||||
|
||||
<div class="text-align-center clear">
|
||||
<input type="hidden" name="logo" data-ls-bind="{{console-project.logo}}" data-read="<?php echo $this->escape(json_encode(['role:all'])); ?>" data-write="<?php echo $this->escape(json_encode(['team:{{console-project.teamId}}'])); ?>" data-accept="image/*" data-forms-upload="" data-label-button="Upload" data-preview-alt="Project Logo" data-scope="console" data-default="">
|
||||
<input type="hidden" name="logo" data-ls-bind="{{console-project.logo}}" data-permissions="<?php echo $this->escape(\json_encode([Permission::read(Role::any()), Permission::update(Role::team('{{console-project.teamId}}')), Permission::delete(Role::team('{{console-project.teamId}}'))])); ?>" data-accept="image/*" data-forms-upload="" data-label-button="Upload" data-preview-alt="Project Logo" data-scope="console" data-default="">
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<!-- <div data-ls-if="0 !== {{console-domains|activeDomainsCount}}">
|
||||
<label for="name">Custom API Endpoints</label>
|
||||
|
||||
<ul data-ls-loop="console-domains" data-ls-as="domain">
|
||||
<li>
|
||||
<div class="input-copy" data-ls-if="true === {{domain.verification}} && {{domain.certificateId}}">
|
||||
<input data-forms-copy type="text" disabled data-ls-bind="{{env.PROTOCOL}}://{{domain.domain}}/v1" />
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div> -->
|
||||
|
||||
|
||||
<button class="" type="submit">Update</button>
|
||||
</form>
|
||||
</div>
|
||||
|
|
@ -144,57 +135,7 @@ $smtpEnabled = $this->getParam('smtpEnabled', false);
|
|||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<!-- <li data-state="/console/settings/privacy?project={{router.params.project}}">
|
||||
|
||||
<form
|
||||
data-service="projects.update"
|
||||
data-scope="console"
|
||||
data-event="submit"
|
||||
data-param-project-id="{{router.params.project}}"
|
||||
data-success="alert,trigger"
|
||||
data-success-param-alert-text="Updated project successfully"
|
||||
data-success-param-trigger-events="projects.update"
|
||||
data-failure="alert"
|
||||
data-failure-param-alert-text="Failed to update project"
|
||||
data-failure-param-alert-classname="error">
|
||||
|
||||
<h2>Privacy & Legal</h2>
|
||||
|
||||
<div class="box margin-bottom">
|
||||
<input name="$id" type="hidden" data-ls-bind="{{console-project.$id}}" />
|
||||
|
||||
<div class="row thin">
|
||||
<div class="col span-6">
|
||||
<label for="legalName">Legal Name</label>
|
||||
<input name="legalName" id="legalName" type="text" autocomplete="off" data-ls-bind="{{console-project.legalName}}" data-forms-text-direction>
|
||||
|
||||
<label for="legalCountry">Country</label>
|
||||
<select id="legalCountry" name="legalCountry" data-ls-bind="{{console-project.legalCountry}}" data-ls-loop="locale-countries" data-ls-as="option">
|
||||
<option data-ls-attrs="value={{$index}}" data-ls-bind="{{option}}"></option>
|
||||
</select>
|
||||
|
||||
<label for="legalCity">City</label>
|
||||
<input name="legalCity" id="legalCity" type="text" autocomplete="off" data-ls-bind="{{console-project.legalCity}}" data-forms-text-direction>
|
||||
</div>
|
||||
|
||||
<div class="col span-6">
|
||||
<label for="legalTaxId">Tax ID</label>
|
||||
<input name="legalTaxId" id="legalTaxId" type="text" autocomplete="off" data-ls-bind="{{console-project.legalTaxId}}" data-forms-text-direction>
|
||||
|
||||
<label for="legalState">State</label>
|
||||
<input name="legalState" id="legalState" type="text" autocomplete="off" data-ls-bind="{{console-project.legalState}}" data-forms-text-direction>
|
||||
|
||||
<label for="legalAddress">Address</label>
|
||||
<input name="legalAddress" id="legalAddress" type="text" autocomplete="off" data-ls-bind="{{console-project.legalAddress}}" data-forms-text-direction>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<button class="" type="submit">Update</button>
|
||||
</div>
|
||||
</form>
|
||||
</li> -->
|
||||
<li data-state="/console/settings/services?project={{router.params.project}}">
|
||||
<h2>Services</h2>
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,9 @@
|
|||
$home = $this->getParam('home', '');
|
||||
$fileLimit = $this->getParam('fileLimit', 0);
|
||||
$fileLimitHuman = $this->getParam('fileLimitHuman', 0);
|
||||
$bucketPermissions = $this->getParam('bucketPermissions', null);
|
||||
$fileCreatePermissions = $this->getParam('fileCreatePermissions', null);
|
||||
$fileUpdatePermissions = $this->getParam('fileUpdatePermissions', null);
|
||||
?>
|
||||
|
||||
<div
|
||||
|
|
@ -34,6 +37,11 @@ $fileLimitHuman = $this->getParam('fileLimitHuman', 0);
|
|||
</div>
|
||||
|
||||
<div class="zone xl">
|
||||
<!-- Required for permission input validation -->
|
||||
<form id="<?php echo $bucketPermissions->getParam('form') ?>"></form>
|
||||
<form id="<?php echo $fileCreatePermissions->getParam('form') ?>"></form>
|
||||
<form id="<?php echo $fileUpdatePermissions->getParam('form') ?>"></form>
|
||||
|
||||
<ul class="phases clear" data-ui-phases data-selected="{{router.params.tab}}">
|
||||
<li data-state="/console/storage/bucket?id={{router.params.id}}&project={{router.params.project}}">
|
||||
<h2 class="margin-bottom">Files</h2>
|
||||
|
|
@ -131,13 +139,14 @@ $fileLimitHuman = $this->getParam('fileLimitHuman', 0);
|
|||
</div>
|
||||
<input type="hidden" data-ls-attrs="id=file-bucketId-{{file.$id}}" name="bucketId" data-ls-bind="{{file.bucketId}}">
|
||||
|
||||
<label for="file-read">Read Access (<a data-ls-attrs="href={{env.HOME}}/docs/permissions" target="_blank" rel="noopener">Learn more</a>)</label>
|
||||
<input type="hidden" data-ls-attrs="id=file-read-{{file.$id}}" name="read" data-forms-tags data-cast-to="json" data-ls-bind="{{file.$read}}" placeholder="User ID, Team ID or Role" />
|
||||
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Add 'role:all' for wildcard access</div>
|
||||
<div class="toggle margin-bottom" data-ls-if="{{project-bucket.fileSecurity}}" data-ls-ui-open data-button-aria="Open Permissions">
|
||||
<i class="icon-plus pull-end margin-top-tiny"></i>
|
||||
<i class="icon-minus pull-end margin-top-tiny"></i>
|
||||
|
||||
<label for="file-write">Write Access (<a data-ls-attrs="href={{env.HOME}}/docs/permissions" target="_blank" rel="noopener">Learn more</a>)</label>
|
||||
<input type="hidden" data-ls-attrs="id=file-write-{{file.$id}}" name="write" data-forms-tags data-cast-to="json" data-ls-bind="{{file.$write}}" placeholder="User ID, Team ID or Role" />
|
||||
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Add 'role:all' for wildcard access</div>
|
||||
<h3 class="margin-bottom-large">Permissions</h3>
|
||||
|
||||
<?php echo $fileUpdatePermissions->render(); ?>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<form class="strip"
|
||||
|
|
@ -190,7 +199,7 @@ $fileLimitHuman = $this->getParam('fileLimitHuman', 0);
|
|||
<span data-ls-bind="{{file.sizeOriginal|humanFileUnit}}"></span>
|
||||
</div>
|
||||
<div class="margin-bottom">
|
||||
<i class="icon-angle-circled-right margin-start-negative-tiny margin-end-tiny"></i> Created at: <span data-ls-bind="{{file.$createdAt|dateText}}"></span>
|
||||
<i class="icon-angle-circled-right margin-start-negative-tiny margin-end-tiny"></i> Created at: <span data-ls-bind="{{file.$createdAt|date}}"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -211,7 +220,7 @@ $fileLimitHuman = $this->getParam('fileLimitHuman', 0);
|
|||
<span class="text-fade text-size-small" data-ls-bind="{{file.sizeOriginal|humanFileUnit}}"></span>
|
||||
</td>
|
||||
<td data-title="Created: ">
|
||||
<span class="text-fade text-size-small" data-ls-bind="{{file.$createdAt|dateText}}"></span>
|
||||
<span class="text-fade text-size-small" data-ls-bind="{{file.$createdAt|date}}"></span>
|
||||
</td>
|
||||
<td data-title="" class="cell-options-more" style="overflow: visible">
|
||||
<div class="drop-list end" data-ls-ui-open="" data-button-aria="File Options" data-button-class="icon-dot-3 reset-inner-button" data-blur="1">
|
||||
|
|
@ -270,8 +279,7 @@ $fileLimitHuman = $this->getParam('fileLimitHuman', 0);
|
|||
data-analytics-category="console"
|
||||
data-analytics-label="Create Storage File"
|
||||
x-data
|
||||
@submit.prevent="$store.uploader.uploadFile($event.target)"
|
||||
>
|
||||
@submit.prevent="$store.uploader.uploadFile($event.target)">
|
||||
<input type="hidden" name="bucketId" id="files-bucketId" data-ls-bind="{{router.params.id}}">
|
||||
|
||||
<label for="fileId">File ID</label>
|
||||
|
|
@ -285,18 +293,19 @@ $fileLimitHuman = $this->getParam('fileLimitHuman', 0);
|
|||
name="fileId"
|
||||
id="fileId" />
|
||||
|
||||
<label for="file-read">File</label>
|
||||
<label for="file">File</label>
|
||||
<input type="file" name="file" id="file-file" size="1" required>
|
||||
|
||||
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">(Max file size allowed: <?php echo $fileLimitHuman; ?>)</div>
|
||||
|
||||
<label for="file-read">Read Access (<a data-ls-attrs="href={{env.HOME}}/docs/permissions" target="_blank" rel="noopener">Learn more</a>)</label>
|
||||
<input type="hidden" id="file-read" name="read" data-forms-tags data-cast-to="json" value="<?php echo htmlentities(json_encode(['role:all'])); ?>" placeholder="User ID, Team ID or Role" />
|
||||
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Add 'role:all' for wildcard access</div>
|
||||
<div class="toggle margin-bottom" data-ls-if="{{project-bucket.fileSecurity}}" data-ls-ui-open data-button-aria="Open Permissions">
|
||||
<i class="icon-plus pull-end margin-top-tiny"></i>
|
||||
<i class="icon-minus pull-end margin-top-tiny"></i>
|
||||
|
||||
<label for="file-write">Write Access (<a data-ls-attrs="href={{env.HOME}}/docs/permissions" target="_blank" rel="noopener">Learn more</a>)</label>
|
||||
<input type="hidden" id="file-write" name="write" data-forms-tags data-cast-to="json" value="" placeholder="User ID, Team ID or Role" />
|
||||
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Add 'role:all' for wildcard access</div>
|
||||
<h3 class="margin-bottom-large">Permissions</h3>
|
||||
|
||||
<?php echo $fileCreatePermissions->render() ?>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<button type="submit">Create</button> <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
|
||||
|
|
@ -381,6 +390,7 @@ $fileLimitHuman = $this->getParam('fileLimitHuman', 0);
|
|||
|
||||
<div class="row responsive margin-top-negative">
|
||||
<div class="col span-8 margin-bottom">
|
||||
|
||||
<form
|
||||
data-analytics
|
||||
data-analytics-activity
|
||||
|
|
@ -426,35 +436,21 @@ $fileLimitHuman = $this->getParam('fileLimitHuman', 0);
|
|||
|
||||
<label class="margin-bottom-small">Permissions</label>
|
||||
|
||||
<p class="text-fade text-size-small">Choose the permissions model for this bucket.</p>
|
||||
<p class="text-fade text-size-small">Configure the permissions for this bucket.</p>
|
||||
|
||||
<hr class="margin-top-small" />
|
||||
|
||||
<div class="row">
|
||||
<div class="col span-1"><input name="permission" value="bucket" type="radio" class="margin-top-tiny" data-ls-bind="{{project-bucket.permission}}" /></div>
|
||||
<div class="col span-11">
|
||||
<b>Bucket Level</b>
|
||||
<p class="text-fade margin-top-tiny">With Bucket Level permissions, you assign permissions only once in the bucket.</p>
|
||||
<p class="text-fade margin-top-tiny">In this permission level permissions assigned to bucket takes the precedence and file permissions are ignored</p>
|
||||
<div data-ls-if="{{project-bucket.permission}} == 'bucket'">
|
||||
<?php echo $bucketPermissions->render(); ?>
|
||||
|
||||
<label for="bucket-read">Read Access <span class="text-size-small">(<a data-ls-attrs="href={{env.HOME}}/docs/permissions" target="_blank" rel="noopener">Learn more</a>)</span></label>
|
||||
<input type="hidden" id="bucket-read" name="read" data-forms-tags data-cast-to="json" data-ls-bind="{{project-bucket.$read}}" placeholder="User ID, Team ID or Role" />
|
||||
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Add 'role:all' for wildcard access</div>
|
||||
<hr class="margin-top-no" />
|
||||
|
||||
<label for="bucket-write">Write Access <span class="text-size-small">(<a data-ls-attrs="href={{env.HOME}}/docs/permissions" target="_blank" rel="noopener">Learn more</a>)</label>
|
||||
<input type="hidden" id="bucket-write" name="write" data-forms-tags data-cast-to="json" data-ls-bind="{{project-bucket.$write}}" placeholder="User ID, Team ID or Role" />
|
||||
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Add 'role:all' for wildcard access</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<label class="margin-bottom-small">File Security</label>
|
||||
|
||||
<div class="row">
|
||||
<div class="col span-1"><input name="permission" value="file" type="radio" class="margin-top-no" data-ls-bind="{{project-bucket.permission}}" /></div>
|
||||
<div class="col span-1"><input name="fileSecurity" value="false" type="checkbox" class="margin-top-no" data-ls-bind="{{project-bucket.fileSecurity}}" /></div>
|
||||
<div class="col span-11">
|
||||
<b>File Level</b>
|
||||
<p class="text-fade margin-top-tiny">With File Level permissions, you have granular access control over every file. Users will only be able to access files for which they have explicit permissions.</p>
|
||||
<p class="text-fade margin-top-tiny">In this permission level file permissions take precedence and bucket permissions are ignored.</p>
|
||||
<b>Enabled</b>
|
||||
<p class="text-fade margin-top-tiny">With File Security enabled, users will be able to access files for which they have been granted <b>either</b> File or Bucket permissions.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -481,8 +477,8 @@ $fileLimitHuman = $this->getParam('fileLimitHuman', 0);
|
|||
View as JSON
|
||||
</button>
|
||||
</li>
|
||||
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> Last Updated: <span data-ls-bind="{{project-bucket.$updatedAt|dateText}}"></span></li>
|
||||
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> Created: <span data-ls-bind="{{project-bucket.$createdAt|dateText}}"></span></li>
|
||||
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> Last Updated: <span data-ls-bind="{{project-bucket.$updatedAt|date}}"></span></li>
|
||||
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> Created: <span data-ls-bind="{{project-bucket.$createdAt|date}}"></span></li>
|
||||
</ul>
|
||||
|
||||
<form name="storage.deleteBucket" class="margin-bottom"
|
||||
|
|
|
|||
|
|
@ -108,9 +108,8 @@
|
|||
<label for="bucket-name">Name</label>
|
||||
<input type="text" class="full-width" id="bucket-name" name="name" required autocomplete="off" maxlength="128" />
|
||||
|
||||
<input type="hidden" id="bucket-permission" name="permission" required value="bucket" />
|
||||
<input type="hidden" id="bucket-read" name="read" required data-cast-to="json" value="<?php echo htmlentities(json_encode([])); ?>" />
|
||||
<input type="hidden" id="bucket-write" name="write" required data-cast-to="json" value="<?php echo htmlentities(json_encode([])); ?>" />
|
||||
<input type="hidden" id="bucket-permissions" name="permissions" required data-cast-to="json" value="<?php echo htmlentities(json_encode([])); ?>" />
|
||||
<input type="hidden" id="bucket-fileSecurity" name="fileSecurity" required value="false" data-cast-to="boolean" />
|
||||
|
||||
<hr />
|
||||
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ $smtpEnabled = $this->getParam('smtpEnabled', false);
|
|||
<span class="tag red">Blocked</span>
|
||||
</span>
|
||||
</td>
|
||||
<td data-title="Created: "><small data-ls-bind="{{user.registration|dateText}}"></small></td>
|
||||
<td data-title="Created: "><small data-ls-bind="{{user.registration|date}}"></small></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
@ -248,7 +248,7 @@ $smtpEnabled = $this->getParam('smtpEnabled', false);
|
|||
<a data-ls-attrs="href=/console/users/teams/team?id={{team.$id}}&project={{router.params.project}}" data-ls-bind="{{team.name}}" data-ls-attrs="title={{team.name}}"></a>
|
||||
</td>
|
||||
<td data-title="Members: "><span data-ls-bind="{{team.total}} members"></span></td>
|
||||
<td data-title="Date Created: "><small data-ls-bind="{{team.$createdAt|dateText}}"></small></td>
|
||||
<td data-title="Date Created: "><small data-ls-bind="{{team.$createdAt|date}}"></small></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
@ -329,8 +329,8 @@ $smtpEnabled = $this->getParam('smtpEnabled', false);
|
|||
</li>
|
||||
|
||||
<li data-state="/console/users/providers?project={{router.params.project}}">
|
||||
<p data-ls-if="{{console-project.authLimit}} == 0" class="text-fade text-size-small margin-bottom pull-end">Unlimited Users <span class="link" data-ls-ui-trigger="project-update-auth-users-limit">Set Limit</a></p>
|
||||
<p data-ls-if="{{console-project.authLimit}} != 0" class="text-fade text-size-small margin-bottom pull-end"><span data-ls-bind="{{console-project.authLimit|statsTotal}}"></span> Users allowed <span class="link" data-ls-ui-trigger="project-update-auth-users-limit">Change Limit</a></p>
|
||||
<p data-ls-if="{{console-project.authLimit}} == 0" class="text-fade text-size-small margin-bottom pull-end">Unlimited Users <span class="link" data-ls-ui-trigger="project-update-auth-users-limit">Set Limit</span></p>
|
||||
<p data-ls-if="{{console-project.authLimit}} != 0" class="text-fade text-size-small margin-bottom pull-end"><span data-ls-bind="{{console-project.authLimit|statsTotal}}"></span> Users allowed <span class="link" data-ls-ui-trigger="project-update-auth-users-limit">Change Limit</span></p>
|
||||
|
||||
<h2>Settings</h2>
|
||||
|
||||
|
|
|
|||
|
|
@ -216,7 +216,7 @@
|
|||
View as JSON
|
||||
</button>
|
||||
</li>
|
||||
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> Created: <span data-ls-bind="{{team.$createdAt|dateText}}"></span></li>
|
||||
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> Created: <span data-ls-bind="{{team.$createdAt|date}}"></span></li>
|
||||
</ul>
|
||||
|
||||
<form name="teams.delete" class="margin-bottom"
|
||||
|
|
|
|||
|
|
@ -183,7 +183,7 @@
|
|||
<div class="text-align-center">
|
||||
<img src="" data-ls-attrs="src={{user|avatar}}" data-size="200" alt="User Avatar" class="avatar huge margin-top-negative-xxl" />
|
||||
|
||||
<div class="margin-top-small margin-bottom-small" data-ls-bind="Member since {{user.registration|dateText}}"></div>
|
||||
<div class="margin-top-small margin-bottom-small" data-ls-bind="Member since {{user.registration|date}}"></div>
|
||||
<hr class="margin-top-tiny margin-bottom-tiny" data-ls-if="{{user.email}}">
|
||||
<div class="margin-top-small margin-bottom-small clear" data-ls-if="{{user.email}}">
|
||||
<span data-ls-bind="{{user.email}}" class="pull-start"></span>
|
||||
|
|
|
|||
|
|
@ -7,8 +7,11 @@ use Appwrite\Utopia\Response\Model\Deployment;
|
|||
use Cron\CronExpression;
|
||||
use Executor\Executor;
|
||||
use Appwrite\Usage\Stats;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\App;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Database\ID;
|
||||
use Utopia\Storage\Storage;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Config\Config;
|
||||
|
|
@ -75,14 +78,12 @@ class BuildsV1 extends Worker
|
|||
}
|
||||
|
||||
$buildId = $deployment->getAttribute('buildId', '');
|
||||
$build = null;
|
||||
$startTime = \time();
|
||||
$startTime = DateTime::now();
|
||||
if (empty($buildId)) {
|
||||
$buildId = $dbForProject->getId();
|
||||
$buildId = ID::unique();
|
||||
$build = $dbForProject->createDocument('builds', new Document([
|
||||
'$id' => $buildId,
|
||||
'$read' => [],
|
||||
'$write' => [],
|
||||
'$permissions' => [],
|
||||
'startTime' => $startTime,
|
||||
'deploymentId' => $deployment->getId(),
|
||||
'status' => 'processing',
|
||||
|
|
@ -92,7 +93,7 @@ class BuildsV1 extends Worker
|
|||
'sourceType' => App::getEnv('_APP_STORAGE_DEVICE', Storage::DEVICE_LOCAL),
|
||||
'stdout' => '',
|
||||
'stderr' => '',
|
||||
'endTime' => 0,
|
||||
'endTime' => null,
|
||||
'duration' => 0
|
||||
]));
|
||||
$deployment->setAttribute('buildId', $buildId);
|
||||
|
|
@ -184,13 +185,14 @@ class BuildsV1 extends Worker
|
|||
/** Update function schedule */
|
||||
$schedule = $function->getAttribute('schedule', '');
|
||||
$cron = (empty($function->getAttribute('deployment')) && !empty($schedule)) ? new CronExpression($schedule) : null;
|
||||
$next = (empty($function->getAttribute('deployment')) && !empty($schedule)) ? $cron->getNextRunDate()->format('U') : 0;
|
||||
$function->setAttribute('scheduleNext', (int)$next);
|
||||
$next = (empty($function->getAttribute('deployment')) && !empty($schedule)) ? DateTime::format($cron->getNextRunDate()) : null;
|
||||
$function->setAttribute('scheduleNext', $next);
|
||||
$function = $dbForProject->updateDocument('functions', $function->getId(), $function);
|
||||
} catch (\Throwable $th) {
|
||||
$endtime = \time();
|
||||
$endtime = DateTime::now();
|
||||
$interval = (new \DateTime($endtime))->diff(new \DateTime($startTime));
|
||||
$build->setAttribute('endTime', $endtime);
|
||||
$build->setAttribute('duration', $endtime - $startTime);
|
||||
$build->setAttribute('duration', $interval->format('%s'));
|
||||
$build->setAttribute('status', 'failed');
|
||||
$build->setAttribute('stderr', $th->getMessage());
|
||||
Console::error($th->getMessage());
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ use Utopia\App;
|
|||
use Utopia\CLI\Console;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Domains\Domain;
|
||||
|
||||
require_once __DIR__ . '/../init.php';
|
||||
|
|
@ -73,7 +73,7 @@ class CertificatesV1 extends Worker
|
|||
$domain = new Domain($document->getAttribute('domain', ''));
|
||||
|
||||
// Get current certificate
|
||||
$certificate = $this->dbForConsole->findOne('certificates', [new Query('domain', Query::TYPE_EQUAL, [$domain->get()])]);
|
||||
$certificate = $this->dbForConsole->findOne('certificates', [Query::equal('domain', [$domain->get()])]);
|
||||
|
||||
// If we don't have certificate for domain yet, let's create new document. At the end we save it
|
||||
if (!$certificate) {
|
||||
|
|
@ -116,7 +116,7 @@ class CertificatesV1 extends Worker
|
|||
// Update certificate info stored in database
|
||||
$certificate->setAttribute('renewDate', $this->getRenewDate($domain->get()));
|
||||
$certificate->setAttribute('attempts', 0);
|
||||
$certificate->setAttribute('issueDate', \time());
|
||||
$certificate->setAttribute('issueDate', DateTime::now());
|
||||
} catch (Throwable $e) {
|
||||
// Set exception as log in certificate document
|
||||
$certificate->setAttribute('log', $e->getMessage());
|
||||
|
|
@ -129,7 +129,7 @@ class CertificatesV1 extends Worker
|
|||
$this->notifyError($domain->get(), $e->getMessage(), $attempts);
|
||||
} finally {
|
||||
// All actions result in new updatedAt date
|
||||
$certificate->setAttribute('updated', \time());
|
||||
$certificate->setAttribute('updated', DateTime::now());
|
||||
|
||||
// Save all changes we made to certificate document into database
|
||||
$this->saveCertificateDocument($domain->get(), $certificate);
|
||||
|
|
@ -151,7 +151,7 @@ class CertificatesV1 extends Worker
|
|||
private function saveCertificateDocument(string $domain, Document $certificate): void
|
||||
{
|
||||
// Check if update or insert required
|
||||
$certificateDocument = $this->dbForConsole->findOne('certificates', [new Query('domain', Query::TYPE_EQUAL, [$domain])]);
|
||||
$certificateDocument = $this->dbForConsole->findOne('certificates', [Query::equal('domain', [$domain])]);
|
||||
if (!empty($certificateDocument) && !$certificateDocument->isEmpty()) {
|
||||
// Merge new data with current data
|
||||
$certificate = new Document(\array_merge($certificateDocument->getArrayCopy(), $certificate->getArrayCopy()));
|
||||
|
|
@ -176,7 +176,7 @@ class CertificatesV1 extends Worker
|
|||
if (!empty($envDomain) && $envDomain !== 'localhost') {
|
||||
return $envDomain;
|
||||
} else {
|
||||
$domainDocument = $this->dbForConsole->findOne('domains', [], 0, ['_id'], ['ASC']);
|
||||
$domainDocument = $this->dbForConsole->findOne('domains', [Query::orderAsc('_id')]);
|
||||
if ($domainDocument) {
|
||||
return $domainDocument->getAttribute('domain');
|
||||
}
|
||||
|
|
@ -296,10 +296,9 @@ class CertificatesV1 extends Worker
|
|||
{
|
||||
$certPath = APP_STORAGE_CERTIFICATES . '/' . $domain . '/cert.pem';
|
||||
$certData = openssl_x509_parse(file_get_contents($certPath));
|
||||
$validTo = $certData['validTo_time_t'] ?? 0;
|
||||
$expiryInAdvance = (60 * 60 * 24 * 30); // 30 days
|
||||
|
||||
return $validTo - $expiryInAdvance;
|
||||
$validTo = $certData['validTo_time_t'] ?? null;
|
||||
$dt = (new \DateTime())->setTimestamp($validTo);
|
||||
return DateTime::addSeconds($dt, -60 * 60 * 24 * 30); // -30 days
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -395,11 +394,12 @@ class CertificatesV1 extends Worker
|
|||
private function updateDomainDocuments(string $certificateId, string $domain): void
|
||||
{
|
||||
$domains = $this->dbForConsole->find('domains', [
|
||||
new Query('domain', Query::TYPE_EQUAL, [$domain])
|
||||
], 1000);
|
||||
Query::equal('domain', [$domain]),
|
||||
Query::limit(1000),
|
||||
]);
|
||||
|
||||
foreach ($domains as $domainDocument) {
|
||||
$domainDocument->setAttribute('updated', \time());
|
||||
$domainDocument->setAttribute('updated', DateTime::now());
|
||||
$domainDocument->setAttribute('certificateId', $certificateId);
|
||||
|
||||
$this->dbForConsole->updateDocument('domains', $domainDocument->getId(), $domainDocument);
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ use Utopia\Cache\Cache;
|
|||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Appwrite\Resque\Worker;
|
||||
use Executor\Executor;
|
||||
use Utopia\Storage\Device\Local;
|
||||
|
|
@ -71,17 +70,17 @@ class DeletesV1 extends Worker
|
|||
break;
|
||||
|
||||
case DELETE_TYPE_EXECUTIONS:
|
||||
$this->deleteExecutionLogs($this->args['timestamp']);
|
||||
$this->deleteExecutionLogs($this->args['datetime']);
|
||||
break;
|
||||
|
||||
case DELETE_TYPE_AUDIT:
|
||||
$timestamp = $this->args['timestamp'] ?? 0;
|
||||
$document = new Document($this->args['document'] ?? []);
|
||||
|
||||
if (!empty($timestamp)) {
|
||||
$this->deleteAuditLogs($this->args['timestamp']);
|
||||
$datetime = $this->args['datetime'] ?? null;
|
||||
if (!empty($datetime)) {
|
||||
$this->deleteAuditLogs($datetime);
|
||||
}
|
||||
|
||||
$document = new Document($this->args['document'] ?? []);
|
||||
|
||||
if (!$document->isEmpty()) {
|
||||
$this->deleteAuditLogsByResource('document/' . $document->getId(), $project->getId());
|
||||
}
|
||||
|
|
@ -89,15 +88,15 @@ class DeletesV1 extends Worker
|
|||
break;
|
||||
|
||||
case DELETE_TYPE_ABUSE:
|
||||
$this->deleteAbuseLogs($this->args['timestamp']);
|
||||
$this->deleteAbuseLogs($this->args['datetime']);
|
||||
break;
|
||||
|
||||
case DELETE_TYPE_REALTIME:
|
||||
$this->deleteRealtimeUsage($this->args['timestamp']);
|
||||
$this->deleteRealtimeUsage($this->args['datetime']);
|
||||
break;
|
||||
|
||||
case DELETE_TYPE_SESSIONS:
|
||||
$this->deleteExpiredSessions($this->args['timestamp']);
|
||||
$this->deleteExpiredSessions($this->args['datetime']);
|
||||
break;
|
||||
|
||||
case DELETE_TYPE_CERTIFICATES:
|
||||
|
|
@ -106,7 +105,7 @@ class DeletesV1 extends Worker
|
|||
break;
|
||||
|
||||
case DELETE_TYPE_USAGE:
|
||||
$this->deleteUsageStats($this->args['timestamp1d'], $this->args['timestamp30m']);
|
||||
$this->deleteUsageStats($this->args['dateTime1d'], $this->args['dateTime30m']);
|
||||
break;
|
||||
|
||||
case DELETE_TYPE_CACHE_BY_RESOURCE:
|
||||
|
|
@ -131,15 +130,15 @@ class DeletesV1 extends Worker
|
|||
protected function deleteCacheByResource(string $projectId): void
|
||||
{
|
||||
$this->deleteCacheFiles([
|
||||
new Query('resource', Query::TYPE_EQUAL, [$this->args['resource']])
|
||||
]);
|
||||
Query::equal('resource', [$this->args['resource']]),
|
||||
]);
|
||||
}
|
||||
|
||||
protected function deleteCacheByTimestamp(): void
|
||||
{
|
||||
$this->deleteCacheFiles([
|
||||
new Query('accessedAt', Query::TYPE_LESSER, [$this->args['timestamp']])
|
||||
]);
|
||||
Query::lessThan('accessedAt', $this->args['timestamp']),
|
||||
]);
|
||||
}
|
||||
|
||||
protected function deleteCacheFiles($query): void
|
||||
|
|
@ -201,33 +200,33 @@ class DeletesV1 extends Worker
|
|||
$dbForProject->deleteCollection('database_' . $databaseId . '_collection_' . $document->getInternalId());
|
||||
|
||||
$this->deleteByGroup('attributes', [
|
||||
new Query('collectionId', Query::TYPE_EQUAL, [$collectionId])
|
||||
Query::equal('collectionId', [$collectionId])
|
||||
], $dbForProject);
|
||||
|
||||
$this->deleteByGroup('indexes', [
|
||||
new Query('collectionId', Query::TYPE_EQUAL, [$collectionId])
|
||||
Query::equal('collectionId', [$collectionId])
|
||||
], $dbForProject);
|
||||
|
||||
$this->deleteAuditLogsByResource('collection/' . $collectionId, $projectId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $timestamp1d
|
||||
* @param int $timestamp30m
|
||||
* @param string $datetime1d
|
||||
* @param string $datetime30m
|
||||
*/
|
||||
protected function deleteUsageStats(int $timestamp1d, int $timestamp30m)
|
||||
protected function deleteUsageStats(string $datetime1d, string $datetime30m)
|
||||
{
|
||||
$this->deleteForProjectIds(function (string $projectId) use ($timestamp1d, $timestamp30m) {
|
||||
$this->deleteForProjectIds(function (string $projectId) use ($datetime1d, $datetime30m) {
|
||||
$dbForProject = $this->getProjectDB($projectId);
|
||||
// Delete Usage stats
|
||||
$this->deleteByGroup('stats', [
|
||||
new Query('time', Query::TYPE_LESSER, [$timestamp1d]),
|
||||
new Query('period', Query::TYPE_EQUAL, ['1d']),
|
||||
Query::lessThan('time', $datetime1d),
|
||||
Query::equal('period', ['1d']),
|
||||
], $dbForProject);
|
||||
|
||||
$this->deleteByGroup('stats', [
|
||||
new Query('time', Query::TYPE_LESSER, [$timestamp30m]),
|
||||
new Query('period', Query::TYPE_EQUAL, ['30m']),
|
||||
Query::lessThan('time', [$datetime30m]),
|
||||
Query::equal('period', ['30m']),
|
||||
], $dbForProject);
|
||||
});
|
||||
}
|
||||
|
|
@ -242,7 +241,7 @@ class DeletesV1 extends Worker
|
|||
|
||||
// Delete Memberships
|
||||
$this->deleteByGroup('memberships', [
|
||||
new Query('teamId', Query::TYPE_EQUAL, [$teamId])
|
||||
Query::equal('teamId', [$teamId])
|
||||
], $this->getProjectDB($projectId));
|
||||
}
|
||||
|
||||
|
|
@ -274,14 +273,14 @@ class DeletesV1 extends Worker
|
|||
|
||||
// Delete all sessions of this user from the sessions table and update the sessions field of the user record
|
||||
$this->deleteByGroup('sessions', [
|
||||
new Query('userId', Query::TYPE_EQUAL, [$userId])
|
||||
Query::equal('userId', [$userId])
|
||||
], $this->getProjectDB($projectId));
|
||||
|
||||
$this->getProjectDB($projectId)->deleteCachedDocument('users', $userId);
|
||||
|
||||
// Delete Memberships and decrement team membership counts
|
||||
$this->deleteByGroup('memberships', [
|
||||
new Query('userId', Query::TYPE_EQUAL, [$userId])
|
||||
Query::equal('userId', [$userId])
|
||||
], $this->getProjectDB($projectId), function (Document $document) use ($projectId) {
|
||||
|
||||
if ($document->getAttribute('confirm')) { // Count only confirmed members
|
||||
|
|
@ -302,67 +301,67 @@ class DeletesV1 extends Worker
|
|||
|
||||
// Delete tokens
|
||||
$this->deleteByGroup('tokens', [
|
||||
new Query('userId', Query::TYPE_EQUAL, [$userId])
|
||||
Query::equal('userId', [$userId])
|
||||
], $this->getProjectDB($projectId));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $timestamp
|
||||
* @param string $datetime
|
||||
*/
|
||||
protected function deleteExecutionLogs(int $timestamp): void
|
||||
protected function deleteExecutionLogs(string $datetime): void
|
||||
{
|
||||
$this->deleteForProjectIds(function (string $projectId) use ($timestamp) {
|
||||
$this->deleteForProjectIds(function (string $projectId) use ($datetime) {
|
||||
$dbForProject = $this->getProjectDB($projectId);
|
||||
// Delete Executions
|
||||
$this->deleteByGroup('executions', [
|
||||
new Query('$createdAt', Query::TYPE_LESSER, [$timestamp])
|
||||
Query::lessThan('$createdAt', $datetime)
|
||||
], $dbForProject);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $timestamp
|
||||
* @param string $datetime
|
||||
*/
|
||||
protected function deleteExpiredSessions(int $timestamp): void
|
||||
protected function deleteExpiredSessions(string $datetime): void
|
||||
{
|
||||
$this->deleteForProjectIds(function (string $projectId) use ($timestamp) {
|
||||
$this->deleteForProjectIds(function (string $projectId) use ($datetime) {
|
||||
$dbForProject = $this->getProjectDB($projectId);
|
||||
// Delete Sessions
|
||||
$this->deleteByGroup('sessions', [
|
||||
new Query('expire', Query::TYPE_LESSER, [$timestamp])
|
||||
], $dbForProject);
|
||||
// Delete Sessions
|
||||
$this->deleteByGroup('sessions', [
|
||||
Query::lessThan('expire', $datetime)
|
||||
], $dbForProject);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $timestamp
|
||||
* @param string $datetime
|
||||
*/
|
||||
protected function deleteRealtimeUsage(int $timestamp): void
|
||||
protected function deleteRealtimeUsage(string $datetime): void
|
||||
{
|
||||
$this->deleteForProjectIds(function (string $projectId) use ($timestamp) {
|
||||
$this->deleteForProjectIds(function (string $projectId) use ($datetime) {
|
||||
$dbForProject = $this->getProjectDB($projectId);
|
||||
// Delete Dead Realtime Logs
|
||||
$this->deleteByGroup('realtime', [
|
||||
new Query('timestamp', Query::TYPE_LESSER, [$timestamp])
|
||||
Query::lessThan('timestamp', $datetime)
|
||||
], $dbForProject);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $timestamp
|
||||
* @param string $datetime
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function deleteAbuseLogs(int $timestamp): void
|
||||
protected function deleteAbuseLogs(string $datetime): void
|
||||
{
|
||||
if ($timestamp == 0) {
|
||||
throw new Exception('Failed to delete audit logs. No timestamp provided');
|
||||
if (empty($datetime)) {
|
||||
throw new Exception('Failed to delete audit logs. No datetime provided');
|
||||
}
|
||||
|
||||
$this->deleteForProjectIds(function (string $projectId) use ($timestamp) {
|
||||
$this->deleteForProjectIds(function (string $projectId) use ($datetime) {
|
||||
$dbForProject = $this->getProjectDB($projectId);
|
||||
$timeLimit = new TimeLimit("", 0, 1, $dbForProject);
|
||||
$abuse = new Abuse($timeLimit);
|
||||
|
||||
$status = $abuse->cleanup($timestamp);
|
||||
$status = $abuse->cleanup($datetime);
|
||||
if (!$status) {
|
||||
throw new Exception('Failed to delete Abuse logs for project ' . $projectId);
|
||||
}
|
||||
|
|
@ -370,17 +369,19 @@ class DeletesV1 extends Worker
|
|||
}
|
||||
|
||||
/**
|
||||
* @param int $timestamp
|
||||
* @param string $datetime
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function deleteAuditLogs(int $timestamp): void
|
||||
protected function deleteAuditLogs(string $datetime): void
|
||||
{
|
||||
if ($timestamp == 0) {
|
||||
throw new Exception('Failed to delete audit logs. No timestamp provided');
|
||||
if (empty($datetime)) {
|
||||
throw new Exception('Failed to delete audit logs. No datetime provided');
|
||||
}
|
||||
$this->deleteForProjectIds(function (string $projectId) use ($timestamp) {
|
||||
|
||||
$this->deleteForProjectIds(function (string $projectId) use ($datetime) {
|
||||
$dbForProject = $this->getProjectDB($projectId);
|
||||
$audit = new Audit($dbForProject);
|
||||
$status = $audit->cleanup($timestamp);
|
||||
$status = $audit->cleanup($datetime);
|
||||
if (!$status) {
|
||||
throw new Exception('Failed to delete Audit logs for project' . $projectId);
|
||||
}
|
||||
|
|
@ -388,14 +389,15 @@ class DeletesV1 extends Worker
|
|||
}
|
||||
|
||||
/**
|
||||
* @param int $timestamp
|
||||
* @param string $resource
|
||||
* @param string $projectId
|
||||
*/
|
||||
protected function deleteAuditLogsByResource(string $resource, string $projectId): void
|
||||
{
|
||||
$dbForProject = $this->getProjectDB($projectId);
|
||||
|
||||
$this->deleteByGroup(Audit::COLLECTION, [
|
||||
new Query('resource', Query::TYPE_EQUAL, [$resource])
|
||||
Query::equal('resource', [$resource])
|
||||
], $dbForProject);
|
||||
}
|
||||
|
||||
|
|
@ -415,7 +417,7 @@ class DeletesV1 extends Worker
|
|||
$storageFunctions = new Local(APP_STORAGE_FUNCTIONS . '/app-' . $projectId);
|
||||
$deploymentIds = [];
|
||||
$this->deleteByGroup('deployments', [
|
||||
new Query('resourceId', Query::TYPE_EQUAL, [$functionId])
|
||||
Query::equal('resourceId', [$functionId])
|
||||
], $dbForProject, function (Document $document) use ($storageFunctions, &$deploymentIds) {
|
||||
$deploymentIds[] = $document->getId();
|
||||
if ($storageFunctions->delete($document->getAttribute('path', ''), true)) {
|
||||
|
|
@ -432,7 +434,7 @@ class DeletesV1 extends Worker
|
|||
$storageBuilds = new Local(APP_STORAGE_BUILDS . '/app-' . $projectId);
|
||||
foreach ($deploymentIds as $deploymentId) {
|
||||
$this->deleteByGroup('builds', [
|
||||
new Query('deploymentId', Query::TYPE_EQUAL, [$deploymentId])
|
||||
Query::equal('deploymentId', [$deploymentId])
|
||||
], $dbForProject, function (Document $document) use ($storageBuilds, $deploymentId) {
|
||||
if ($storageBuilds->delete($document->getAttribute('outputPath', ''), true)) {
|
||||
Console::success('Deleted build files: ' . $document->getAttribute('outputPath', ''));
|
||||
|
|
@ -447,7 +449,7 @@ class DeletesV1 extends Worker
|
|||
*/
|
||||
Console::info("Deleting executions for function " . $functionId);
|
||||
$this->deleteByGroup('executions', [
|
||||
new Query('functionId', Query::TYPE_EQUAL, [$functionId])
|
||||
Query::equal('functionId', [$functionId])
|
||||
], $dbForProject);
|
||||
|
||||
/**
|
||||
|
|
@ -491,7 +493,7 @@ class DeletesV1 extends Worker
|
|||
Console::info("Deleting builds for deployment " . $deploymentId);
|
||||
$storageBuilds = new Local(APP_STORAGE_BUILDS . '/app-' . $projectId);
|
||||
$this->deleteByGroup('builds', [
|
||||
new Query('deploymentId', Query::TYPE_EQUAL, [$deploymentId])
|
||||
Query::equal('deploymentId', [$deploymentId])
|
||||
], $dbForProject, function (Document $document) use ($storageBuilds) {
|
||||
if ($storageBuilds->delete($document->getAttribute('outputPath', ''), true)) {
|
||||
Console::success('Deleted build files: ' . $document->getAttribute('outputPath', ''));
|
||||
|
|
@ -550,7 +552,7 @@ class DeletesV1 extends Worker
|
|||
$executionStart = \microtime(true);
|
||||
|
||||
while ($sum === $limit) {
|
||||
$projects = $this->getConsoleDB()->find('projects', [], $limit, ($chunk * $limit));
|
||||
$projects = $this->getConsoleDB()->find('projects', [Query::limit($limit), Query::offset($chunk * $limit)]);
|
||||
|
||||
$chunk++;
|
||||
|
||||
|
|
@ -589,7 +591,7 @@ class DeletesV1 extends Worker
|
|||
while ($sum === $limit) {
|
||||
$chunk++;
|
||||
|
||||
$results = $database->find($collection, $queries, $limit, 0);
|
||||
$results = $database->find($collection, \array_merge([Query::limit($limit)], $queries));
|
||||
|
||||
$sum = count($results);
|
||||
|
||||
|
|
@ -616,7 +618,7 @@ class DeletesV1 extends Worker
|
|||
// If domain has certificate generated
|
||||
if (isset($document['certificateId'])) {
|
||||
$domainUsingCertificate = $consoleDB->findOne('domains', [
|
||||
new Query('certificateId', Query::TYPE_EQUAL, [$document['certificateId']])
|
||||
Query::equal('certificateId', [$document['certificateId']])
|
||||
]);
|
||||
|
||||
if (!$domainUsingCertificate) {
|
||||
|
|
|
|||
|
|
@ -12,8 +12,12 @@ use Utopia\App;
|
|||
use Utopia\CLI\Console;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\ID;
|
||||
use Utopia\Database\Permission;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Role;
|
||||
|
||||
require_once __DIR__ . '/../init.php';
|
||||
|
||||
|
|
@ -61,7 +65,11 @@ class FunctionsV1 extends Worker
|
|||
/** @var Document[] $functions */
|
||||
|
||||
while ($sum >= $limit) {
|
||||
$functions = $database->find('functions', [], $limit, $offset, ['name'], [Database::ORDER_ASC]);
|
||||
$functions = $database->find('functions', [
|
||||
Query::limit($limit),
|
||||
Query::offset($offset),
|
||||
Query::orderAsc('name'),
|
||||
]);
|
||||
$sum = \count($functions);
|
||||
$offset = $offset + $limit;
|
||||
|
||||
|
|
@ -147,31 +155,26 @@ class FunctionsV1 extends Worker
|
|||
}
|
||||
|
||||
$cron = new CronExpression($function->getAttribute('schedule'));
|
||||
$next = (int) $cron->getNextRunDate()->format('U');
|
||||
$next = DateTime::format($cron->getNextRunDate());
|
||||
|
||||
$function
|
||||
->setAttribute('scheduleNext', $next)
|
||||
->setAttribute('schedulePrevious', \time());
|
||||
->setAttribute('schedulePrevious', DateTime::now());
|
||||
|
||||
$function = $database->updateDocument(
|
||||
'functions',
|
||||
$function->getId(),
|
||||
$function->setAttribute('scheduleNext', (int) $next)
|
||||
$function->setAttribute('scheduleNext', $next)
|
||||
);
|
||||
|
||||
if ($function === false) {
|
||||
throw new Exception('Function update failed.');
|
||||
}
|
||||
|
||||
$reschedule = new Func();
|
||||
$reschedule
|
||||
->setFunction($function)
|
||||
->setType('schedule')
|
||||
->setUser($user)
|
||||
->setProject($project);
|
||||
|
||||
// Async task reschedule
|
||||
$reschedule->schedule($next);
|
||||
->setProject($project)
|
||||
->schedule(new \DateTime($next));
|
||||
;
|
||||
|
||||
$this->execute(
|
||||
project: $project,
|
||||
|
|
@ -234,11 +237,10 @@ class FunctionsV1 extends Worker
|
|||
/** Create execution or update execution status */
|
||||
$execution = $dbForProject->getDocument('executions', $executionId ?? '');
|
||||
if ($execution->isEmpty()) {
|
||||
$executionId = $dbForProject->getId();
|
||||
$executionId = ID::unique();
|
||||
$execution = $dbForProject->createDocument('executions', new Document([
|
||||
'$id' => $executionId,
|
||||
'$read' => $user->isEmpty() ? [] : ['user:' . $user->getId()],
|
||||
'$write' => [],
|
||||
'$permissions' => $user->isEmpty() ? [] : [Permission::read(Role::user($user->getId()))],
|
||||
'functionId' => $functionId,
|
||||
'deploymentId' => $deploymentId,
|
||||
'trigger' => $trigger,
|
||||
|
|
@ -297,10 +299,9 @@ class FunctionsV1 extends Worker
|
|||
->setAttribute('stderr', $executionResponse['stderr'])
|
||||
->setAttribute('time', $executionResponse['time']);
|
||||
} catch (\Throwable $th) {
|
||||
$endtime = \microtime(true);
|
||||
$time = $endtime - $execution->getCreatedAt();
|
||||
$interval = (new \DateTime())->diff(new \DateTime($execution->getCreatedAt()));
|
||||
$execution
|
||||
->setAttribute('time', $time)
|
||||
->setAttribute('time', (float)$interval->format('%s.%f'))
|
||||
->setAttribute('status', 'failed')
|
||||
->setAttribute('statusCode', $th->getCode())
|
||||
->setAttribute('stderr', $th->getMessage());
|
||||
|
|
|
|||
|
|
@ -45,13 +45,13 @@
|
|||
"appwrite/php-runtimes": "0.11.*",
|
||||
"utopia-php/framework": "0.21.*",
|
||||
"utopia-php/logger": "0.3.*",
|
||||
"utopia-php/abuse": "0.7.*",
|
||||
"utopia-php/abuse": "0.12.*",
|
||||
"utopia-php/analytics": "0.2.*",
|
||||
"utopia-php/audit": "0.8.*",
|
||||
"utopia-php/audit": "0.13.*",
|
||||
"utopia-php/cache": "0.6.*",
|
||||
"utopia-php/cli": "0.13.*",
|
||||
"utopia-php/config": "0.2.*",
|
||||
"utopia-php/database": "0.18.*",
|
||||
"utopia-php/database": "0.24.*",
|
||||
"utopia-php/locale": "0.4.*",
|
||||
"utopia-php/registry": "0.5.*",
|
||||
"utopia-php/preloader": "0.2.*",
|
||||
|
|
@ -77,7 +77,8 @@
|
|||
}
|
||||
],
|
||||
"require-dev": {
|
||||
"appwrite/sdk-generator": "0.20.0",
|
||||
"ext-fileinfo": "*",
|
||||
"appwrite/sdk-generator": "dev-master as 0.19.5",
|
||||
"phpunit/phpunit": "9.5.20",
|
||||
"squizlabs/php_codesniffer": "^3.6",
|
||||
"swoole/ide-helper": "4.8.9",
|
||||
|
|
|
|||
143
composer.lock
generated
143
composer.lock
generated
|
|
@ -4,7 +4,7 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "80fda8d774d2c31a3daf2d4c2290d83d",
|
||||
"content-hash": "1145ff29befcc4aa21b5002da0b8319c",
|
||||
"packages": [
|
||||
{
|
||||
"name": "adhocore/jwt",
|
||||
|
|
@ -481,16 +481,16 @@
|
|||
},
|
||||
{
|
||||
"name": "guzzlehttp/guzzle",
|
||||
"version": "7.4.5",
|
||||
"version": "7.5.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/guzzle/guzzle.git",
|
||||
"reference": "1dd98b0564cb3f6bd16ce683cb755f94c10fbd82"
|
||||
"reference": "b50a2a1251152e43f6a37f0fa053e730a67d25ba"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/1dd98b0564cb3f6bd16ce683cb755f94c10fbd82",
|
||||
"reference": "1dd98b0564cb3f6bd16ce683cb755f94c10fbd82",
|
||||
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/b50a2a1251152e43f6a37f0fa053e730a67d25ba",
|
||||
"reference": "b50a2a1251152e43f6a37f0fa053e730a67d25ba",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -505,10 +505,10 @@
|
|||
"psr/http-client-implementation": "1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"bamarni/composer-bin-plugin": "^1.4.1",
|
||||
"bamarni/composer-bin-plugin": "^1.8.1",
|
||||
"ext-curl": "*",
|
||||
"php-http/client-integration-tests": "^3.0",
|
||||
"phpunit/phpunit": "^8.5.5 || ^9.3.5",
|
||||
"phpunit/phpunit": "^8.5.29 || ^9.5.23",
|
||||
"psr/log": "^1.1 || ^2.0 || ^3.0"
|
||||
},
|
||||
"suggest": {
|
||||
|
|
@ -518,8 +518,12 @@
|
|||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"bamarni-bin": {
|
||||
"bin-links": true,
|
||||
"forward-command": false
|
||||
},
|
||||
"branch-alias": {
|
||||
"dev-master": "7.4-dev"
|
||||
"dev-master": "7.5-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
|
|
@ -585,7 +589,7 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/guzzle/guzzle/issues",
|
||||
"source": "https://github.com/guzzle/guzzle/tree/7.4.5"
|
||||
"source": "https://github.com/guzzle/guzzle/tree/7.5.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
|
@ -601,20 +605,20 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-06-20T22:16:13+00:00"
|
||||
"time": "2022-08-28T15:39:27+00:00"
|
||||
},
|
||||
{
|
||||
"name": "guzzlehttp/promises",
|
||||
"version": "1.5.1",
|
||||
"version": "1.5.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/guzzle/promises.git",
|
||||
"reference": "fe752aedc9fd8fcca3fe7ad05d419d32998a06da"
|
||||
"reference": "b94b2807d85443f9719887892882d0329d1e2598"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/guzzle/promises/zipball/fe752aedc9fd8fcca3fe7ad05d419d32998a06da",
|
||||
"reference": "fe752aedc9fd8fcca3fe7ad05d419d32998a06da",
|
||||
"url": "https://api.github.com/repos/guzzle/promises/zipball/b94b2807d85443f9719887892882d0329d1e2598",
|
||||
"reference": "b94b2807d85443f9719887892882d0329d1e2598",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -669,7 +673,7 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/guzzle/promises/issues",
|
||||
"source": "https://github.com/guzzle/promises/tree/1.5.1"
|
||||
"source": "https://github.com/guzzle/promises/tree/1.5.2"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
|
@ -685,20 +689,20 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2021-10-22T20:56:57+00:00"
|
||||
"time": "2022-08-28T14:55:35+00:00"
|
||||
},
|
||||
{
|
||||
"name": "guzzlehttp/psr7",
|
||||
"version": "2.4.0",
|
||||
"version": "2.4.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/guzzle/psr7.git",
|
||||
"reference": "13388f00956b1503577598873fffb5ae994b5737"
|
||||
"reference": "69568e4293f4fa993f3b0e51c9723e1e17c41379"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/guzzle/psr7/zipball/13388f00956b1503577598873fffb5ae994b5737",
|
||||
"reference": "13388f00956b1503577598873fffb5ae994b5737",
|
||||
"url": "https://api.github.com/repos/guzzle/psr7/zipball/69568e4293f4fa993f3b0e51c9723e1e17c41379",
|
||||
"reference": "69568e4293f4fa993f3b0e51c9723e1e17c41379",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -712,15 +716,19 @@
|
|||
"psr/http-message-implementation": "1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"bamarni/composer-bin-plugin": "^1.4.1",
|
||||
"bamarni/composer-bin-plugin": "^1.8.1",
|
||||
"http-interop/http-factory-tests": "^0.9",
|
||||
"phpunit/phpunit": "^8.5.8 || ^9.3.10"
|
||||
"phpunit/phpunit": "^8.5.29 || ^9.5.23"
|
||||
},
|
||||
"suggest": {
|
||||
"laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"bamarni-bin": {
|
||||
"bin-links": true,
|
||||
"forward-command": false
|
||||
},
|
||||
"branch-alias": {
|
||||
"dev-master": "2.4-dev"
|
||||
}
|
||||
|
|
@ -784,7 +792,7 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/guzzle/psr7/issues",
|
||||
"source": "https://github.com/guzzle/psr7/tree/2.4.0"
|
||||
"source": "https://github.com/guzzle/psr7/tree/2.4.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
|
@ -800,7 +808,7 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-06-20T21:43:11+00:00"
|
||||
"time": "2022-08-28T14:45:39+00:00"
|
||||
},
|
||||
{
|
||||
"name": "influxdb/influxdb-php",
|
||||
|
|
@ -1733,22 +1741,23 @@
|
|||
},
|
||||
{
|
||||
"name": "utopia-php/abuse",
|
||||
"version": "0.7.0",
|
||||
"version": "0.12.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/abuse.git",
|
||||
"reference": "52fb20e39e2e9619948bc0a73b52e10caa71350d"
|
||||
"reference": "aa1e1aae163ecf8ea81d48857ff55c241dcb695f"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/abuse/zipball/52fb20e39e2e9619948bc0a73b52e10caa71350d",
|
||||
"reference": "52fb20e39e2e9619948bc0a73b52e10caa71350d",
|
||||
"url": "https://api.github.com/repos/utopia-php/abuse/zipball/aa1e1aae163ecf8ea81d48857ff55c241dcb695f",
|
||||
"reference": "aa1e1aae163ecf8ea81d48857ff55c241dcb695f",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-curl": "*",
|
||||
"ext-pdo": "*",
|
||||
"php": ">=8.0",
|
||||
"utopia-php/database": ">=0.11 <1.0"
|
||||
"utopia-php/database": "0.24.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^9.4",
|
||||
|
|
@ -1780,9 +1789,9 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/abuse/issues",
|
||||
"source": "https://github.com/utopia-php/abuse/tree/0.7.0"
|
||||
"source": "https://github.com/utopia-php/abuse/tree/0.12.0"
|
||||
},
|
||||
"time": "2021-12-27T13:06:45+00:00"
|
||||
"time": "2022-08-27T09:50:09+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/analytics",
|
||||
|
|
@ -1841,22 +1850,22 @@
|
|||
},
|
||||
{
|
||||
"name": "utopia-php/audit",
|
||||
"version": "0.8.0",
|
||||
"version": "0.13.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/audit.git",
|
||||
"reference": "b46dc42614a69437c45eb229249b6a6d000122c1"
|
||||
"reference": "a2f30ccfba7a61b1718b9ebd4557ed0d8a4dcb5b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/audit/zipball/b46dc42614a69437c45eb229249b6a6d000122c1",
|
||||
"reference": "b46dc42614a69437c45eb229249b6a6d000122c1",
|
||||
"url": "https://api.github.com/repos/utopia-php/audit/zipball/a2f30ccfba7a61b1718b9ebd4557ed0d8a4dcb5b",
|
||||
"reference": "a2f30ccfba7a61b1718b9ebd4557ed0d8a4dcb5b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-pdo": "*",
|
||||
"php": ">=8.0",
|
||||
"utopia-php/database": ">=0.11 <1.0"
|
||||
"utopia-php/database": "0.24.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^9.3",
|
||||
|
|
@ -1888,9 +1897,9 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/audit/issues",
|
||||
"source": "https://github.com/utopia-php/audit/tree/0.8.0"
|
||||
"source": "https://github.com/utopia-php/audit/tree/0.13.0"
|
||||
},
|
||||
"time": "2021-12-27T13:05:56+00:00"
|
||||
"time": "2022-08-27T09:18:57+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/cache",
|
||||
|
|
@ -2051,16 +2060,16 @@
|
|||
},
|
||||
{
|
||||
"name": "utopia-php/database",
|
||||
"version": "0.18.9",
|
||||
"version": "0.24.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/database.git",
|
||||
"reference": "227b3ca919149b7b0d6556c8effe9ee46ed081e6"
|
||||
"reference": "7da841d65d87e9f2c242589e58c38880def44dd8"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/database/zipball/227b3ca919149b7b0d6556c8effe9ee46ed081e6",
|
||||
"reference": "227b3ca919149b7b0d6556c8effe9ee46ed081e6",
|
||||
"url": "https://api.github.com/repos/utopia-php/database/zipball/7da841d65d87e9f2c242589e58c38880def44dd8",
|
||||
"reference": "7da841d65d87e9f2c242589e58c38880def44dd8",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -2109,9 +2118,9 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/database/issues",
|
||||
"source": "https://github.com/utopia-php/database/tree/0.18.9"
|
||||
"source": "https://github.com/utopia-php/database/tree/0.24.0"
|
||||
},
|
||||
"time": "2022-07-19T09:42:53+00:00"
|
||||
"time": "2022-08-27T09:16:05+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/domains",
|
||||
|
|
@ -2828,16 +2837,16 @@
|
|||
"packages-dev": [
|
||||
{
|
||||
"name": "appwrite/sdk-generator",
|
||||
"version": "0.20.0",
|
||||
"version": "dev-master",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/appwrite/sdk-generator.git",
|
||||
"reference": "af7dd08848a78a0d38befa3e63083eb0186806f7"
|
||||
"reference": "6e630a62f522ac68a7056bebf81cd032c7a053ba"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/af7dd08848a78a0d38befa3e63083eb0186806f7",
|
||||
"reference": "af7dd08848a78a0d38befa3e63083eb0186806f7",
|
||||
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/6e630a62f522ac68a7056bebf81cd032c7a053ba",
|
||||
"reference": "6e630a62f522ac68a7056bebf81cd032c7a053ba",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -2852,6 +2861,7 @@
|
|||
"brianium/paratest": "^6.4",
|
||||
"phpunit/phpunit": "^9.5.21"
|
||||
},
|
||||
"default-branch": true,
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
|
@ -2872,9 +2882,9 @@
|
|||
"description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms",
|
||||
"support": {
|
||||
"issues": "https://github.com/appwrite/sdk-generator/issues",
|
||||
"source": "https://github.com/appwrite/sdk-generator/tree/0.20.0"
|
||||
"source": "https://github.com/appwrite/sdk-generator/tree/master"
|
||||
},
|
||||
"time": "2022-08-02T10:09:48+00:00"
|
||||
"time": "2022-08-29T10:43:33+00:00"
|
||||
},
|
||||
{
|
||||
"name": "doctrine/instantiator",
|
||||
|
|
@ -4800,16 +4810,16 @@
|
|||
},
|
||||
{
|
||||
"name": "sebastian/type",
|
||||
"version": "3.0.0",
|
||||
"version": "3.1.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/type.git",
|
||||
"reference": "b233b84bc4465aff7b57cf1c4bc75c86d00d6dad"
|
||||
"reference": "fb44e1cc6e557418387ad815780360057e40753e"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/type/zipball/b233b84bc4465aff7b57cf1c4bc75c86d00d6dad",
|
||||
"reference": "b233b84bc4465aff7b57cf1c4bc75c86d00d6dad",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/type/zipball/fb44e1cc6e557418387ad815780360057e40753e",
|
||||
"reference": "fb44e1cc6e557418387ad815780360057e40753e",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -4821,7 +4831,7 @@
|
|||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "3.0-dev"
|
||||
"dev-master": "3.1-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
|
|
@ -4844,7 +4854,7 @@
|
|||
"homepage": "https://github.com/sebastianbergmann/type",
|
||||
"support": {
|
||||
"issues": "https://github.com/sebastianbergmann/type/issues",
|
||||
"source": "https://github.com/sebastianbergmann/type/tree/3.0.0"
|
||||
"source": "https://github.com/sebastianbergmann/type/tree/3.1.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
|
@ -4852,7 +4862,7 @@
|
|||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2022-03-15T09:54:48+00:00"
|
||||
"time": "2022-08-29T06:55:37+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sebastian/version",
|
||||
|
|
@ -5346,9 +5356,18 @@
|
|||
"time": "2022-08-12T06:47:24+00:00"
|
||||
}
|
||||
],
|
||||
"aliases": [],
|
||||
"aliases": [
|
||||
{
|
||||
"package": "appwrite/sdk-generator",
|
||||
"version": "9999999-dev",
|
||||
"alias": "0.19.5",
|
||||
"alias_normalized": "0.19.5.0"
|
||||
}
|
||||
],
|
||||
"minimum-stability": "stable",
|
||||
"stability-flags": [],
|
||||
"stability-flags": {
|
||||
"appwrite/sdk-generator": 20
|
||||
},
|
||||
"prefer-stable": false,
|
||||
"prefer-lowest": false,
|
||||
"platform": {
|
||||
|
|
@ -5366,9 +5385,11 @@
|
|||
"ext-zlib": "*",
|
||||
"ext-sockets": "*"
|
||||
},
|
||||
"platform-dev": [],
|
||||
"platform-dev": {
|
||||
"ext-fileinfo": "*"
|
||||
},
|
||||
"platform-overrides": {
|
||||
"php": "8.0"
|
||||
},
|
||||
"plugin-api-version": "2.2.0"
|
||||
"plugin-api-version": "2.3.0"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -559,6 +559,7 @@ services:
|
|||
volumes:
|
||||
- ./app:/usr/src/code/app
|
||||
- ./src:/usr/src/code/src
|
||||
#- ./vendor/utopia-php/database:/usr/src/code/vendor/utopia-php/database
|
||||
depends_on:
|
||||
- redis
|
||||
environment:
|
||||
|
|
|
|||
|
|
@ -342,7 +342,7 @@
|
|||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
|
||||
Guset
|
||||
<br/>
|
||||
role:guest
|
||||
guests
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -363,7 +363,7 @@
|
|||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
|
||||
Member
|
||||
<br/>
|
||||
role:member
|
||||
users
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 64 KiB |
|
|
@ -35,6 +35,7 @@ const configApp = {
|
|||
'public/scripts/app.js',
|
||||
'public/scripts/upload-modal.js',
|
||||
'public/scripts/events.js',
|
||||
'public/scripts/permissions-matrix.js',
|
||||
|
||||
'public/scripts/views/service.js',
|
||||
|
||||
|
|
|
|||
|
|
@ -35,4 +35,4 @@
|
|||
<file>./tests/e2e/Services/Functions/FunctionsCustomClientTest.php</file>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
</phpunit>
|
||||
</phpunit>
|
||||
|
|
|
|||
225
public/dist/scripts/app-all.js
vendored
225
public/dist/scripts/app-all.js
vendored
|
|
@ -9,9 +9,9 @@ static flatten(data,prefix=''){let output={};for(const key in data){let value=da
|
|||
else{output[finalKey]=value;}}
|
||||
return output;}}
|
||||
Service.CHUNK_SIZE=5*1024*1024;class Query{}
|
||||
Query.equal=(attribute,value)=>Query.addQuery(attribute,'equal',value);Query.notEqual=(attribute,value)=>Query.addQuery(attribute,'notEqual',value);Query.lesser=(attribute,value)=>Query.addQuery(attribute,'lesser',value);Query.lesserEqual=(attribute,value)=>Query.addQuery(attribute,'lesserEqual',value);Query.greater=(attribute,value)=>Query.addQuery(attribute,'greater',value);Query.greaterEqual=(attribute,value)=>Query.addQuery(attribute,'greaterEqual',value);Query.search=(attribute,value)=>Query.addQuery(attribute,'search',value);Query.addQuery=(attribute,oper,value)=>value instanceof Array?`${attribute}.${oper}(${value
|
||||
Query.equal=(attribute,value)=>Query.addQuery(attribute,"equal",value);Query.notEqual=(attribute,value)=>Query.addQuery(attribute,"notEqual",value);Query.lessThan=(attribute,value)=>Query.addQuery(attribute,"lessThan",value);Query.lessThanEqual=(attribute,value)=>Query.addQuery(attribute,"lessThanEqual",value);Query.greaterThan=(attribute,value)=>Query.addQuery(attribute,"greaterThan",value);Query.greaterThanEqual=(attribute,value)=>Query.addQuery(attribute,"greaterThanEqual",value);Query.search=(attribute,value)=>Query.addQuery(attribute,"search",value);Query.orderDesc=(attribute)=>`orderDesc("${attribute}")`;Query.orderAsc=(attribute)=>`orderAsc("${attribute}")`;Query.cursorAfter=(documentId)=>`cursorAfter("${documentId}")`;Query.cursorBefore=(documentId)=>`cursorBefore("${documentId}")`;Query.limit=(limit)=>`limit(${limit})`;Query.offset=(offset)=>`offset(${offset})`;Query.addQuery=(attribute,method,value)=>value instanceof Array?`${method}("${attribute}", [${value
|
||||
.map((v) => Query.parseValues(v))
|
||||
.join(',')})`:`${attribute}.${oper}(${Query.parseValues(value)})`;Query.parseValues=(value)=>typeof value==='string'||value instanceof String?`"${value}"`:`${value}`;class AppwriteException extends Error{constructor(message,code=0,type='',response=''){super(message);this.name='AppwriteException';this.message=message;this.code=code;this.type=type;this.response=response;}}
|
||||
.join(",")}])`:`${method}("${attribute}", [${Query.parseValues(value)}])`;Query.parseValues=(value)=>typeof value==="string"||value instanceof String?`"${value}"`:`${value}`;class AppwriteException extends Error{constructor(message,code=0,type='',response=''){super(message);this.name='AppwriteException';this.message=message;this.code=code;this.type=type;this.response=response;}}
|
||||
class Client{constructor(){this.config={endpoint:'https://HOSTNAME/v1',endpointRealtime:'',project:'',key:'',jwt:'',locale:'',mode:'',};this.headers={'x-sdk-version':'appwrite:web:6.0.0','X-Appwrite-Response-Format':'0.15.0',};this.realtime={socket:undefined,timeout:undefined,url:'',channels:new Set(),subscriptions:new Map(),subscriptionsCounter:0,reconnect:true,reconnectAttempts:0,lastMessage:undefined,connect:()=>{clearTimeout(this.realtime.timeout);this.realtime.timeout=window===null||window===void 0?void 0:window.setTimeout(()=>{this.realtime.createSocket();},50);},getTimeout:()=>{switch(true){case this.realtime.reconnectAttempts<5:return 1000;case this.realtime.reconnectAttempts<15:return 5000;case this.realtime.reconnectAttempts<100:return 10000;default:return 60000;}},createSocket:()=>{var _a,_b;if(this.realtime.channels.size<1)
|
||||
return;const channels=new URLSearchParams();channels.set('project',this.config.project);this.realtime.channels.forEach(channel=>{channels.append('channels[]',channel);});const url=this.config.endpointRealtime+'/realtime?'+channels.toString();if(url!==this.realtime.url||!this.realtime.socket||((_a=this.realtime.socket)===null||_a===void 0?void 0:_a.readyState)>WebSocket.OPEN){if(this.realtime.socket&&((_b=this.realtime.socket)===null||_b===void 0?void 0:_b.readyState)<WebSocket.CLOSING){this.realtime.reconnect=false;this.realtime.socket.close();}
|
||||
this.realtime.url=url;this.realtime.socket=new WebSocket(url);this.realtime.socket.addEventListener('message',this.realtime.onMessage);this.realtime.socket.addEventListener('open',_event=>{this.realtime.reconnectAttempts=0;});this.realtime.socket.addEventListener('close',event=>{var _a,_b,_c;if(!this.realtime.reconnect||(((_b=(_a=this.realtime)===null||_a===void 0?void 0:_a.lastMessage)===null||_b===void 0?void 0:_b.type)==='error'&&((_c=this.realtime)===null||_c===void 0?void 0:_c.lastMessage.data).code===1008)){this.realtime.reconnect=true;return;}
|
||||
|
|
@ -220,29 +220,26 @@ if(typeof cursor!=='undefined'){payload['cursor']=cursor;}
|
|||
if(typeof cursorDirection!=='undefined'){payload['cursorDirection']=cursorDirection;}
|
||||
if(typeof orderType!=='undefined'){payload['orderType']=orderType;}
|
||||
const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('get',uri,{'content-type':'application/json',},payload);});}
|
||||
createCollection(databaseId,collectionId,name,permission,read,write){return __awaiter(this,void 0,void 0,function*(){if(typeof databaseId==='undefined'){throw new AppwriteException('Missing required parameter: "databaseId"');}
|
||||
createCollection(databaseId,collectionId,name,permissions,documentSecurity){return __awaiter(this,void 0,void 0,function*(){if(typeof databaseId==='undefined'){throw new AppwriteException('Missing required parameter: "databaseId"');}
|
||||
if(typeof collectionId==='undefined'){throw new AppwriteException('Missing required parameter: "collectionId"');}
|
||||
if(typeof name==='undefined'){throw new AppwriteException('Missing required parameter: "name"');}
|
||||
if(typeof permission==='undefined'){throw new AppwriteException('Missing required parameter: "permission"');}
|
||||
if(typeof read==='undefined'){throw new AppwriteException('Missing required parameter: "read"');}
|
||||
if(typeof write==='undefined'){throw new AppwriteException('Missing required parameter: "write"');}
|
||||
if(typeof permissions==='undefined'){throw new AppwriteException('Missing required parameter: "permissions"');}
|
||||
if(typeof documentSecurity==='undefined'){throw new AppwriteException('Missing required parameter: "documentSecurity"');}
|
||||
let path='/databases/{databaseId}/collections'.replace('{databaseId}',databaseId);let payload={};if(typeof collectionId!=='undefined'){payload['collectionId']=collectionId;}
|
||||
if(typeof name!=='undefined'){payload['name']=name;}
|
||||
if(typeof permission!=='undefined'){payload['permission']=permission;}
|
||||
if(typeof read!=='undefined'){payload['read']=read;}
|
||||
if(typeof write!=='undefined'){payload['write']=write;}
|
||||
if(typeof permissions!=='undefined'){payload['permissions']=permissions;}
|
||||
if(typeof documentSecurity!=='undefined'){payload['documentSecurity']=documentSecurity;}
|
||||
const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('post',uri,{'content-type':'application/json',},payload);});}
|
||||
getCollection(databaseId,collectionId){return __awaiter(this,void 0,void 0,function*(){if(typeof databaseId==='undefined'){throw new AppwriteException('Missing required parameter: "databaseId"');}
|
||||
if(typeof collectionId==='undefined'){throw new AppwriteException('Missing required parameter: "collectionId"');}
|
||||
let path='/databases/{databaseId}/collections/{collectionId}'.replace('{databaseId}',databaseId).replace('{collectionId}',collectionId);let payload={};const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('get',uri,{'content-type':'application/json',},payload);});}
|
||||
updateCollection(databaseId,collectionId,name,permission,read,write,enabled){return __awaiter(this,void 0,void 0,function*(){if(typeof databaseId==='undefined'){throw new AppwriteException('Missing required parameter: "databaseId"');}
|
||||
updateCollection(databaseId,collectionId,name,documentSecurity,permissions,enabled){return __awaiter(this,void 0,void 0,function*(){if(typeof databaseId==='undefined'){throw new AppwriteException('Missing required parameter: "databaseId"');}
|
||||
if(typeof collectionId==='undefined'){throw new AppwriteException('Missing required parameter: "collectionId"');}
|
||||
if(typeof name==='undefined'){throw new AppwriteException('Missing required parameter: "name"');}
|
||||
if(typeof permission==='undefined'){throw new AppwriteException('Missing required parameter: "permission"');}
|
||||
if(typeof documentSecurity==='undefined'){throw new AppwriteException('Missing required parameter: "documentSecurity"');}
|
||||
let path='/databases/{databaseId}/collections/{collectionId}'.replace('{databaseId}',databaseId).replace('{collectionId}',collectionId);let payload={};if(typeof name!=='undefined'){payload['name']=name;}
|
||||
if(typeof permission!=='undefined'){payload['permission']=permission;}
|
||||
if(typeof read!=='undefined'){payload['read']=read;}
|
||||
if(typeof write!=='undefined'){payload['write']=write;}
|
||||
if(typeof permissions!=='undefined'){payload['permissions']=permissions;}
|
||||
if(typeof documentSecurity!=='undefined'){payload['documentSecurity']=documentSecurity;}
|
||||
if(typeof enabled!=='undefined'){payload['enabled']=enabled;}
|
||||
const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('put',uri,{'content-type':'application/json',},payload);});}
|
||||
deleteCollection(databaseId,collectionId){return __awaiter(this,void 0,void 0,function*(){if(typeof databaseId==='undefined'){throw new AppwriteException('Missing required parameter: "databaseId"');}
|
||||
|
|
@ -260,6 +257,15 @@ if(typeof required!=='undefined'){payload['required']=required;}
|
|||
if(typeof xdefault!=='undefined'){payload['default']=xdefault;}
|
||||
if(typeof array!=='undefined'){payload['array']=array;}
|
||||
const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('post',uri,{'content-type':'application/json',},payload);});}
|
||||
createDatetimeAttribute(databaseId,collectionId,key,required,xdefault,array){return __awaiter(this,void 0,void 0,function*(){if(typeof databaseId==='undefined'){throw new AppwriteException('Missing required parameter: "databaseId"');}
|
||||
if(typeof collectionId==='undefined'){throw new AppwriteException('Missing required parameter: "collectionId"');}
|
||||
if(typeof key==='undefined'){throw new AppwriteException('Missing required parameter: "key"');}
|
||||
if(typeof required==='undefined'){throw new AppwriteException('Missing required parameter: "required"');}
|
||||
let path='/databases/{databaseId}/collections/{collectionId}/attributes/datetime'.replace('{databaseId}',databaseId).replace('{collectionId}',collectionId);let payload={};if(typeof key!=='undefined'){payload['key']=key;}
|
||||
if(typeof required!=='undefined'){payload['required']=required;}
|
||||
if(typeof xdefault!=='undefined'){payload['default']=xdefault;}
|
||||
if(typeof array!=='undefined'){payload['array']=array;}
|
||||
const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('post',uri,{'content-type':'application/json',},payload);});}
|
||||
createEmailAttribute(databaseId,collectionId,key,required,xdefault,array){return __awaiter(this,void 0,void 0,function*(){if(typeof databaseId==='undefined'){throw new AppwriteException('Missing required parameter: "databaseId"');}
|
||||
if(typeof collectionId==='undefined'){throw new AppwriteException('Missing required parameter: "collectionId"');}
|
||||
if(typeof key==='undefined'){throw new AppwriteException('Missing required parameter: "key"');}
|
||||
|
|
@ -349,25 +355,23 @@ if(typeof cursorDirection!=='undefined'){payload['cursorDirection']=cursorDirect
|
|||
if(typeof orderAttributes!=='undefined'){payload['orderAttributes']=orderAttributes;}
|
||||
if(typeof orderTypes!=='undefined'){payload['orderTypes']=orderTypes;}
|
||||
const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('get',uri,{'content-type':'application/json',},payload);});}
|
||||
createDocument(databaseId,collectionId,documentId,data,read,write){return __awaiter(this,void 0,void 0,function*(){if(typeof databaseId==='undefined'){throw new AppwriteException('Missing required parameter: "databaseId"');}
|
||||
createDocument(databaseId,collectionId,documentId,data,permissions){return __awaiter(this,void 0,void 0,function*(){if(typeof databaseId==='undefined'){throw new AppwriteException('Missing required parameter: "databaseId"');}
|
||||
if(typeof collectionId==='undefined'){throw new AppwriteException('Missing required parameter: "collectionId"');}
|
||||
if(typeof documentId==='undefined'){throw new AppwriteException('Missing required parameter: "documentId"');}
|
||||
if(typeof data==='undefined'){throw new AppwriteException('Missing required parameter: "data"');}
|
||||
let path='/databases/{databaseId}/collections/{collectionId}/documents'.replace('{databaseId}',databaseId).replace('{collectionId}',collectionId);let payload={};if(typeof documentId!=='undefined'){payload['documentId']=documentId;}
|
||||
if(typeof data!=='undefined'){payload['data']=data;}
|
||||
if(typeof read!=='undefined'){payload['read']=read;}
|
||||
if(typeof write!=='undefined'){payload['write']=write;}
|
||||
if(typeof permissions!=='undefined'){payload['permissions']=permissions;}
|
||||
const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('post',uri,{'content-type':'application/json',},payload);});}
|
||||
getDocument(databaseId,collectionId,documentId){return __awaiter(this,void 0,void 0,function*(){if(typeof databaseId==='undefined'){throw new AppwriteException('Missing required parameter: "databaseId"');}
|
||||
if(typeof collectionId==='undefined'){throw new AppwriteException('Missing required parameter: "collectionId"');}
|
||||
if(typeof documentId==='undefined'){throw new AppwriteException('Missing required parameter: "documentId"');}
|
||||
let path='/databases/{databaseId}/collections/{collectionId}/documents/{documentId}'.replace('{databaseId}',databaseId).replace('{collectionId}',collectionId).replace('{documentId}',documentId);let payload={};const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('get',uri,{'content-type':'application/json',},payload);});}
|
||||
updateDocument(databaseId,collectionId,documentId,data,read,write){return __awaiter(this,void 0,void 0,function*(){if(typeof databaseId==='undefined'){throw new AppwriteException('Missing required parameter: "databaseId"');}
|
||||
updateDocument(databaseId,collectionId,documentId,data,permissions){return __awaiter(this,void 0,void 0,function*(){if(typeof databaseId==='undefined'){throw new AppwriteException('Missing required parameter: "databaseId"');}
|
||||
if(typeof collectionId==='undefined'){throw new AppwriteException('Missing required parameter: "collectionId"');}
|
||||
if(typeof documentId==='undefined'){throw new AppwriteException('Missing required parameter: "documentId"');}
|
||||
let path='/databases/{databaseId}/collections/{collectionId}/documents/{documentId}'.replace('{databaseId}',databaseId).replace('{collectionId}',collectionId).replace('{documentId}',documentId);let payload={};if(typeof data!=='undefined'){payload['data']=data;}
|
||||
if(typeof read!=='undefined'){payload['read']=read;}
|
||||
if(typeof write!=='undefined'){payload['write']=write;}
|
||||
if(typeof permissions!=='undefined'){payload['permissions']=permissions;}
|
||||
const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('patch',uri,{'content-type':'application/json',},payload);});}
|
||||
deleteDocument(databaseId,collectionId,documentId){return __awaiter(this,void 0,void 0,function*(){if(typeof databaseId==='undefined'){throw new AppwriteException('Missing required parameter: "databaseId"');}
|
||||
if(typeof collectionId==='undefined'){throw new AppwriteException('Missing required parameter: "collectionId"');}
|
||||
|
|
@ -698,14 +702,13 @@ if(typeof cursor!=='undefined'){payload['cursor']=cursor;}
|
|||
if(typeof cursorDirection!=='undefined'){payload['cursorDirection']=cursorDirection;}
|
||||
if(typeof orderType!=='undefined'){payload['orderType']=orderType;}
|
||||
const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('get',uri,{'content-type':'application/json',},payload);});}
|
||||
createBucket(bucketId,name,permission,read,write,enabled,maximumFileSize,allowedFileExtensions,encryption,antivirus){return __awaiter(this,void 0,void 0,function*(){if(typeof bucketId==='undefined'){throw new AppwriteException('Missing required parameter: "bucketId"');}
|
||||
createBucket(bucketId,name,fileSecurity,permissions,enabled,maximumFileSize,allowedFileExtensions,encryption,antivirus){return __awaiter(this,void 0,void 0,function*(){if(typeof bucketId==='undefined'){throw new AppwriteException('Missing required parameter: "bucketId"');}
|
||||
if(typeof name==='undefined'){throw new AppwriteException('Missing required parameter: "name"');}
|
||||
if(typeof permission==='undefined'){throw new AppwriteException('Missing required parameter: "permission"');}
|
||||
if(typeof fileSecurity==='undefined'){throw new AppwriteException('Missing required parameter: "fileSecurity"');}
|
||||
let path='/storage/buckets';let payload={};if(typeof bucketId!=='undefined'){payload['bucketId']=bucketId;}
|
||||
if(typeof name!=='undefined'){payload['name']=name;}
|
||||
if(typeof permission!=='undefined'){payload['permission']=permission;}
|
||||
if(typeof read!=='undefined'){payload['read']=read;}
|
||||
if(typeof write!=='undefined'){payload['write']=write;}
|
||||
if(typeof permissions!=='undefined'){payload['permissions']=permissions;}
|
||||
if(typeof fileSecurity!=='undefined'){payload['fileSecurity']=fileSecurity;}
|
||||
if(typeof enabled!=='undefined'){payload['enabled']=enabled;}
|
||||
if(typeof maximumFileSize!=='undefined'){payload['maximumFileSize']=maximumFileSize;}
|
||||
if(typeof allowedFileExtensions!=='undefined'){payload['allowedFileExtensions']=allowedFileExtensions;}
|
||||
|
|
@ -714,13 +717,12 @@ if(typeof antivirus!=='undefined'){payload['antivirus']=antivirus;}
|
|||
const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('post',uri,{'content-type':'application/json',},payload);});}
|
||||
getBucket(bucketId){return __awaiter(this,void 0,void 0,function*(){if(typeof bucketId==='undefined'){throw new AppwriteException('Missing required parameter: "bucketId"');}
|
||||
let path='/storage/buckets/{bucketId}'.replace('{bucketId}',bucketId);let payload={};const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('get',uri,{'content-type':'application/json',},payload);});}
|
||||
updateBucket(bucketId,name,permission,read,write,enabled,maximumFileSize,allowedFileExtensions,encryption,antivirus){return __awaiter(this,void 0,void 0,function*(){if(typeof bucketId==='undefined'){throw new AppwriteException('Missing required parameter: "bucketId"');}
|
||||
updateBucket(bucketId,name,fileSecurity,permissions,enabled,maximumFileSize,allowedFileExtensions,encryption,antivirus){return __awaiter(this,void 0,void 0,function*(){if(typeof bucketId==='undefined'){throw new AppwriteException('Missing required parameter: "bucketId"');}
|
||||
if(typeof name==='undefined'){throw new AppwriteException('Missing required parameter: "name"');}
|
||||
if(typeof permission==='undefined'){throw new AppwriteException('Missing required parameter: "permission"');}
|
||||
if(typeof fileSecurity==='undefined'){throw new AppwriteException('Missing required parameter: "fileSecurity"');}
|
||||
let path='/storage/buckets/{bucketId}'.replace('{bucketId}',bucketId);let payload={};if(typeof name!=='undefined'){payload['name']=name;}
|
||||
if(typeof permission!=='undefined'){payload['permission']=permission;}
|
||||
if(typeof read!=='undefined'){payload['read']=read;}
|
||||
if(typeof write!=='undefined'){payload['write']=write;}
|
||||
if(typeof permissions!=='undefined'){payload['permissions']=permissions;}
|
||||
if(typeof fileSecurity!=='undefined'){payload['fileSecurity']=fileSecurity;}
|
||||
if(typeof enabled!=='undefined'){payload['enabled']=enabled;}
|
||||
if(typeof maximumFileSize!=='undefined'){payload['maximumFileSize']=maximumFileSize;}
|
||||
if(typeof allowedFileExtensions!=='undefined'){payload['allowedFileExtensions']=allowedFileExtensions;}
|
||||
|
|
@ -737,13 +739,12 @@ if(typeof cursor!=='undefined'){payload['cursor']=cursor;}
|
|||
if(typeof cursorDirection!=='undefined'){payload['cursorDirection']=cursorDirection;}
|
||||
if(typeof orderType!=='undefined'){payload['orderType']=orderType;}
|
||||
const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('get',uri,{'content-type':'application/json',},payload);});}
|
||||
createFile(bucketId,fileId,file,read,write,onProgress=(progress)=>{}){return __awaiter(this,void 0,void 0,function*(){if(typeof bucketId==='undefined'){throw new AppwriteException('Missing required parameter: "bucketId"');}
|
||||
createFile(bucketId,fileId,file,permissions,onProgress=(progress)=>{}){return __awaiter(this,void 0,void 0,function*(){if(typeof bucketId==='undefined'){throw new AppwriteException('Missing required parameter: "bucketId"');}
|
||||
if(typeof fileId==='undefined'){throw new AppwriteException('Missing required parameter: "fileId"');}
|
||||
if(typeof file==='undefined'){throw new AppwriteException('Missing required parameter: "file"');}
|
||||
let path='/storage/buckets/{bucketId}/files'.replace('{bucketId}',bucketId);let payload={};if(typeof fileId!=='undefined'){payload['fileId']=fileId;}
|
||||
if(typeof file!=='undefined'){payload['file']=file;}
|
||||
if(typeof read!=='undefined'){payload['read']=read;}
|
||||
if(typeof write!=='undefined'){payload['write']=write;}
|
||||
if(typeof permissions!=='undefined'){payload['permissions']=permissions;}
|
||||
const uri=new URL(this.client.config.endpoint+path);if(!(file instanceof File)){throw new AppwriteException('Parameter "file" has to be a File.');}
|
||||
const size=file.size;if(size<=Service.CHUNK_SIZE){return yield this.client.call('post',uri,{'content-type':'multipart/form-data',},payload);}
|
||||
let id=undefined;let response=undefined;const headers={'content-type':'multipart/form-data',};let counter=0;const totalCounters=Math.ceil(size/Service.CHUNK_SIZE);if(fileId!='unique()'){try{response=yield this.client.call('GET',new URL(this.client.config.endpoint+path+'/'+fileId),headers);counter=response.chunksUploaded;}
|
||||
|
|
@ -755,10 +756,9 @@ return response;});}
|
|||
getFile(bucketId,fileId){return __awaiter(this,void 0,void 0,function*(){if(typeof bucketId==='undefined'){throw new AppwriteException('Missing required parameter: "bucketId"');}
|
||||
if(typeof fileId==='undefined'){throw new AppwriteException('Missing required parameter: "fileId"');}
|
||||
let path='/storage/buckets/{bucketId}/files/{fileId}'.replace('{bucketId}',bucketId).replace('{fileId}',fileId);let payload={};const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('get',uri,{'content-type':'application/json',},payload);});}
|
||||
updateFile(bucketId,fileId,read,write){return __awaiter(this,void 0,void 0,function*(){if(typeof bucketId==='undefined'){throw new AppwriteException('Missing required parameter: "bucketId"');}
|
||||
updateFile(bucketId,fileId,permissions){return __awaiter(this,void 0,void 0,function*(){if(typeof bucketId==='undefined'){throw new AppwriteException('Missing required parameter: "bucketId"');}
|
||||
if(typeof fileId==='undefined'){throw new AppwriteException('Missing required parameter: "fileId"');}
|
||||
let path='/storage/buckets/{bucketId}/files/{fileId}'.replace('{bucketId}',bucketId).replace('{fileId}',fileId);let payload={};if(typeof read!=='undefined'){payload['read']=read;}
|
||||
if(typeof write!=='undefined'){payload['write']=write;}
|
||||
let path='/storage/buckets/{bucketId}/files/{fileId}'.replace('{bucketId}',bucketId).replace('{fileId}',fileId);let payload={};if(typeof permissions!=='undefined'){payload['permissions']=permissions;}
|
||||
const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('put',uri,{'content-type':'application/json',},payload);});}
|
||||
deleteFile(bucketId,fileId){return __awaiter(this,void 0,void 0,function*(){if(typeof bucketId==='undefined'){throw new AppwriteException('Missing required parameter: "bucketId"');}
|
||||
if(typeof fileId==='undefined'){throw new AppwriteException('Missing required parameter: "fileId"');}
|
||||
|
|
@ -994,7 +994,17 @@ updatePhoneVerification(userId,phoneVerification){return __awaiter(this,void 0,v
|
|||
if(typeof phoneVerification==='undefined'){throw new AppwriteException('Missing required parameter: "phoneVerification"');}
|
||||
let path='/users/{userId}/verification/phone'.replace('{userId}',userId);let payload={};if(typeof phoneVerification!=='undefined'){payload['phoneVerification']=phoneVerification;}
|
||||
const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('patch',uri,{'content-type':'application/json',},payload);});}}
|
||||
exports.Account=Account;exports.AppwriteException=AppwriteException;exports.Avatars=Avatars;exports.Client=Client;exports.Databases=Databases;exports.Functions=Functions;exports.Health=Health;exports.Locale=Locale;exports.Projects=Projects;exports.Query=Query;exports.Storage=Storage;exports.Teams=Teams;exports.Users=Users;Object.defineProperty(exports,'__esModule',{value:true});})(this.Appwrite=this.Appwrite||{},null,window);(function(global,factory){typeof exports==='object'&&typeof module!=='undefined'?module.exports=factory():typeof define==='function'&&define.amd?define(factory):(global=typeof globalThis!=='undefined'?globalThis:global||self,global.Chart=factory());})(this,(function(){'use strict';function noop(){}
|
||||
class Permission{}
|
||||
Permission.read=(role)=>{return`read("${role}")`;};Permission.write=(role)=>{return`write("${role}")`;};Permission.create=(role)=>{return`create("${role}")`;};Permission.update=(role)=>{return`update("${role}")`;};Permission.delete=(role)=>{return`delete("${role}")`;};class Role{static any(){return'any';}
|
||||
static user(id){return`user:${id}`;}
|
||||
static users(){return'users';}
|
||||
static guests(){return'guests';}
|
||||
static team(id,role=''){if(role===''){return`team:${id}`;}
|
||||
return`team:${id}/${role}`;}
|
||||
static status(status){return`status:${status}`;}}
|
||||
class ID{static custom(id){return id;}
|
||||
static unique(){return'unique()';}}
|
||||
exports.Account=Account;exports.AppwriteException=AppwriteException;exports.Avatars=Avatars;exports.Client=Client;exports.Databases=Databases;exports.Functions=Functions;exports.Health=Health;exports.ID=ID;exports.Locale=Locale;exports.Permission=Permission;exports.Projects=Projects;exports.Query=Query;exports.Role=Role;exports.Storage=Storage;exports.Teams=Teams;exports.Users=Users;Object.defineProperty(exports,'__esModule',{value:true});})(this.Appwrite=this.Appwrite||{},null,window);(function(global,factory){typeof exports==='object'&&typeof module!=='undefined'?module.exports=factory():typeof define==='function'&&define.amd?define(factory):(global=typeof globalThis!=='undefined'?globalThis:global||self,global.Chart=factory());})(this,(function(){'use strict';function noop(){}
|
||||
const uid=(function(){let id=0;return function(){return id++;};}());function isNullOrUndef(value){return value===null||typeof value==='undefined';}
|
||||
function isArray(value){if(Array.isArray&&Array.isArray(value)){return true;}
|
||||
const type=Object.prototype.toString.call(value);if(type.slice(0,7)==='[object'&&type.slice(-6)==='Array]'){return true;}
|
||||
|
|
@ -3956,115 +3966,15 @@ params=formData;break;}
|
|||
return new Promise(function(resolve,reject){let request=new XMLHttpRequest(),key;request.withCredentials=true;request.open(method,path,true);for(key in headers){if(headers.hasOwnProperty(key)){request.setRequestHeader(key,headers[key]);}}
|
||||
request.onload=function(){if(4===request.readyState&&399>=request.status){let data=request.response;let contentType=this.getResponseHeader('content-type');contentType=contentType.substring(0,contentType.indexOf(';'));switch(contentType){case'application/json':data=JSON.parse(data);break;}
|
||||
resolve(data);}else{reject(new Error(request.statusText));}};if(progress){request.addEventListener('progress',progress);request.upload.addEventListener('progress',progress,false);}
|
||||
request.onerror=function(){reject(new Error("Network Error"));};request.send(params);})};return{'get':function(path,headers={},params={}){return call('GET',path+((params.length>0)?'?'+buildQuery(params):''),headers,{});},'post':function(path,headers={},params={},progress=null){return call('POST',path,headers,params,progress);},'put':function(path,headers={},params={},progress=null){return call('PUT',headers,params,progress);},'patch':function(path,headers={},params={},progress=null){return call('PATCH',path,headers,params,progress);},'delete':function(path,headers={},params={},progress=null){return call('DELETE',path,headers,params,progress);},'addGlobalParam':addGlobalParam,'addGlobalHeader':addGlobalHeader}}(window.document);let analytics={create:function(id,source,activity,url){return http.post('/analytics',{'content-type':'application/json'},{id:id,source:source,activity:activity,url:url,version:env.VERSION,setup:env.SETUP});},};return{analytics:analytics,};},true);})(window);(function(window){"use strict";window.ls.container.set('console',function(window){var client=new Appwrite.Client();var endpoint=window.location.origin+'/v1';client.setEndpoint(endpoint).setProject('console').setLocale(APP_ENV.LOCALE);return{client:client,account:new Appwrite.Account(client),avatars:new Appwrite.Avatars(client),databases:new Appwrite.Databases(client),functions:new Appwrite.Functions(client),health:new Appwrite.Health(client),locale:new Appwrite.Locale(client),projects:new Appwrite.Projects(client),storage:new Appwrite.Storage(client),teams:new Appwrite.Teams(client),users:new Appwrite.Users(client)}},true);})(window);(function(window){"use strict";window.ls.container.set('date',function(){function format(format,timestamp){var jsdate,f
|
||||
var txtWords=['Sun','Mon','Tues','Wednes','Thurs','Fri','Satur','January','February','March','April','May','June','July','August','September','October','November','December']
|
||||
var formatChr=/\\?(.?)/gi
|
||||
var formatChrCb=function(t,s){return f[t]?f[t]():s}
|
||||
var _pad=function(n,c){n=String(n)
|
||||
while(n.length<c){n='0'+n}
|
||||
return n}
|
||||
f={d:function(){return _pad(f.j(),2)},D:function(){return f.l().slice(0,3)},j:function(){return jsdate.getDate()},l:function(){return txtWords[f.w()]+'day'},N:function(){return f.w()||7},S:function(){var j=f.j()
|
||||
var i=j%10
|
||||
if(i<=3&&parseInt((j%100)/10,10)===1){i=0}
|
||||
return['st','nd','rd'][i-1]||'th'},w:function(){return jsdate.getDay()},z:function(){var a=new Date(f.Y(),f.n()-1,f.j())
|
||||
var b=new Date(f.Y(),0,1)
|
||||
return Math.round((a-b)/864e5)},W:function(){var a=new Date(f.Y(),f.n()-1,f.j()-f.N()+3)
|
||||
var b=new Date(a.getFullYear(),0,4)
|
||||
return _pad(1+Math.round((a-b)/864e5/7),2)},F:function(){return txtWords[6+f.n()]},m:function(){return _pad(f.n(),2)},M:function(){return f.F().slice(0,3)},n:function(){return jsdate.getMonth()+1},t:function(){return(new Date(f.Y(),f.n(),0)).getDate()},L:function(){var j=f.Y()
|
||||
return j%4===0&j%100!==0|j%400===0},o:function(){var n=f.n()
|
||||
var W=f.W()
|
||||
var Y=f.Y()
|
||||
return Y+(n===12&&W<9?1:n===1&&W>9?-1:0)},Y:function(){return jsdate.getFullYear()},y:function(){return f.Y().toString().slice(-2)},a:function(){return jsdate.getHours()>11?'pm':'am'},A:function(){return f.a().toUpperCase()},B:function(){var H=jsdate.getUTCHours()*36e2
|
||||
var i=jsdate.getUTCMinutes()*60
|
||||
var s=jsdate.getUTCSeconds()
|
||||
return _pad(Math.floor((H+i+s+36e2)/86.4)%1e3,3)},g:function(){return f.G()%12||12},G:function(){return jsdate.getHours()},h:function(){return _pad(f.g(),2)},H:function(){return _pad(f.G(),2)},i:function(){return _pad(jsdate.getMinutes(),2)},s:function(){return _pad(jsdate.getSeconds(),2)},u:function(){return _pad(jsdate.getMilliseconds()*1000,6)},e:function(){var msg='Not supported (see source code of date() for timezone on how to add support)'
|
||||
throw new Error(msg)},I:function(){var a=new Date(f.Y(),0)
|
||||
var c=Date.UTC(f.Y(),0)
|
||||
var b=new Date(f.Y(),6)
|
||||
var d=Date.UTC(f.Y(),6)
|
||||
return((a-c)!==(b-d))?1:0},O:function(){var tzo=jsdate.getTimezoneOffset()
|
||||
var a=Math.abs(tzo)
|
||||
return(tzo>0?'-':'+')+_pad(Math.floor(a/60)*100+a%60,4)},P:function(){var O=f.O()
|
||||
return(O.substr(0,3)+':'+O.substr(3,2))},T:function(){return'UTC'},Z:function(){return-jsdate.getTimezoneOffset()*60},c:function(){return'Y-m-d\\TH:i:sP'.replace(formatChr,formatChrCb)},r:function(){return'D, d M Y H:i:s O'.replace(formatChr,formatChrCb)},U:function(){return jsdate/1000|0}}
|
||||
var _date=function(format,timestamp){jsdate=(timestamp===undefined?new Date():(timestamp instanceof Date)?new Date(timestamp):new Date(timestamp*1000))
|
||||
return format.replace(formatChr,formatChrCb)}
|
||||
return _date(format,timestamp)}
|
||||
function strtotime(text,now){var parsed
|
||||
var match
|
||||
var today
|
||||
var year
|
||||
var date
|
||||
var days
|
||||
var ranges
|
||||
var len
|
||||
var times
|
||||
var regex
|
||||
var i
|
||||
var fail=false
|
||||
if(!text){return fail}
|
||||
text=text.replace(/^\s+|\s+$/g,'').replace(/\s{2,}/g,' ').replace(/[\t\r\n]/g,'').toLowerCase()
|
||||
var pattern=new RegExp(['^(\\d{1,4})','([\\-\\.\\/:])','(\\d{1,2})','([\\-\\.\\/:])','(\\d{1,4})','(?:\\s(\\d{1,2}):(\\d{2})?:?(\\d{2})?)?','(?:\\s([A-Z]+)?)?$'].join(''))
|
||||
match=text.match(pattern)
|
||||
if(match&&match[2]===match[4]){if(match[1]>1901){switch(match[2]){case'-':if(match[3]>12||match[5]>31){return fail}
|
||||
return new Date(match[1],parseInt(match[3],10)-1,match[5],match[6]||0,match[7]||0,match[8]||0,match[9]||0)/1000
|
||||
case'.':return fail
|
||||
case'/':if(match[3]>12||match[5]>31){return fail}
|
||||
return new Date(match[1],parseInt(match[3],10)-1,match[5],match[6]||0,match[7]||0,match[8]||0,match[9]||0)/1000}}else if(match[5]>1901){switch(match[2]){case'-':if(match[3]>12||match[1]>31){return fail}
|
||||
return new Date(match[5],parseInt(match[3],10)-1,match[1],match[6]||0,match[7]||0,match[8]||0,match[9]||0)/1000
|
||||
case'.':if(match[3]>12||match[1]>31){return fail}
|
||||
return new Date(match[5],parseInt(match[3],10)-1,match[1],match[6]||0,match[7]||0,match[8]||0,match[9]||0)/1000
|
||||
case'/':if(match[1]>12||match[3]>31){return fail}
|
||||
return new Date(match[5],parseInt(match[1],10)-1,match[3],match[6]||0,match[7]||0,match[8]||0,match[9]||0)/1000}}else{switch(match[2]){case'-':if(match[3]>12||match[5]>31||(match[1]<70&&match[1]>38)){return fail}
|
||||
year=match[1]>=0&&match[1]<=38?+match[1]+2000:match[1]
|
||||
return new Date(year,parseInt(match[3],10)-1,match[5],match[6]||0,match[7]||0,match[8]||0,match[9]||0)/1000
|
||||
case'.':if(match[5]>=70){if(match[3]>12||match[1]>31){return fail}
|
||||
return new Date(match[5],parseInt(match[3],10)-1,match[1],match[6]||0,match[7]||0,match[8]||0,match[9]||0)/1000}
|
||||
if(match[5]<60&&!match[6]){if(match[1]>23||match[3]>59){return fail}
|
||||
today=new Date()
|
||||
return new Date(today.getFullYear(),today.getMonth(),today.getDate(),match[1]||0,match[3]||0,match[5]||0,match[9]||0)/1000}
|
||||
return fail
|
||||
case'/':if(match[1]>12||match[3]>31||(match[5]<70&&match[5]>38)){return fail}
|
||||
year=match[5]>=0&&match[5]<=38?+match[5]+2000:match[5]
|
||||
return new Date(year,parseInt(match[1],10)-1,match[3],match[6]||0,match[7]||0,match[8]||0,match[9]||0)/1000
|
||||
case':':if(match[1]>23||match[3]>59||match[5]>59){return fail}
|
||||
today=new Date()
|
||||
return new Date(today.getFullYear(),today.getMonth(),today.getDate(),match[1]||0,match[3]||0,match[5]||0)/1000}}}
|
||||
if(text==='now'){return now===null||isNaN(now)?new Date().getTime()/1000|0:now|0}
|
||||
if(!isNaN(parsed=Date.parse(text))){return parsed/1000|0}
|
||||
pattern=new RegExp(['^([0-9]{4}-[0-9]{2}-[0-9]{2})','[ t]','([0-9]{2}:[0-9]{2}:[0-9]{2}(\\.[0-9]+)?)','([\\+-][0-9]{2}(:[0-9]{2})?|z)'].join(''))
|
||||
match=text.match(pattern)
|
||||
if(match){if(match[4]==='z'){match[4]='Z'}else if(match[4].match(/^([+-][0-9]{2})$/)){match[4]=match[4]+':00'}
|
||||
if(!isNaN(parsed=Date.parse(match[1]+'T'+match[2]+match[4]))){return parsed/1000|0}}
|
||||
date=now?new Date(now*1000):new Date()
|
||||
days={'sun':0,'mon':1,'tue':2,'wed':3,'thu':4,'fri':5,'sat':6}
|
||||
ranges={'yea':'FullYear','mon':'Month','day':'Date','hou':'Hours','min':'Minutes','sec':'Seconds'}
|
||||
function lastNext(type,range,modifier){var diff
|
||||
var day=days[range]
|
||||
if(typeof day!=='undefined'){diff=day-date.getDay()
|
||||
if(diff===0){diff=7*modifier}else if(diff>0&&type==='last'){diff-=7}else if(diff<0&&type==='next'){diff+=7}
|
||||
date.setDate(date.getDate()+diff)}}
|
||||
function process(val){var splt=val.split(' ')
|
||||
var type=splt[0]
|
||||
var range=splt[1].substring(0,3)
|
||||
var typeIsNumber=/\d+/.test(type)
|
||||
var ago=splt[2]==='ago'
|
||||
var num=(type==='last'?-1:1)*(ago?-1:1)
|
||||
if(typeIsNumber){num*=parseInt(type,10)}
|
||||
if(ranges.hasOwnProperty(range)&&!splt[1].match(/^mon(day|\.)?$/i)){return date['set'+ranges[range]](date['get'+ranges[range]]()+num)}
|
||||
if(range==='wee'){return date.setDate(date.getDate()+(num*7))}
|
||||
if(type==='next'||type==='last'){lastNext(type,range,num)}else if(!typeIsNumber){return false}
|
||||
return true}
|
||||
times='(years?|months?|weeks?|days?|hours?|minutes?|min|seconds?|sec'+'|sunday|sun\\.?|monday|mon\\.?|tuesday|tue\\.?|wednesday|wed\\.?'+'|thursday|thu\\.?|friday|fri\\.?|saturday|sat\\.?)'
|
||||
regex='([+-]?\\d+\\s'+times+'|'+'(last|next)\\s'+times+')(\\sago)?'
|
||||
match=text.match(new RegExp(regex,'gi'))
|
||||
if(!match){return fail}
|
||||
for(i=0,len=match.length;i<len;i++){if(!process(match[i])){return fail}}
|
||||
return(date.getTime()/1000)}
|
||||
return{format:format,strtotime:strtotime}}(),true);})(window);(function(window){"use strict";window.ls.container.set('env',function(){return APP_ENV;},true);})(window);(function(window){"use strict";window.ls.container.set('form',function(){function cast(value,to){if(value&&Array.isArray(value)&&to!=='array'){value=value.map(element=>cast(element,to));return value;}
|
||||
request.onerror=function(){reject(new Error("Network Error"));};request.send(params);})};return{'get':function(path,headers={},params={}){return call('GET',path+((params.length>0)?'?'+buildQuery(params):''),headers,{});},'post':function(path,headers={},params={},progress=null){return call('POST',path,headers,params,progress);},'put':function(path,headers={},params={},progress=null){return call('PUT',headers,params,progress);},'patch':function(path,headers={},params={},progress=null){return call('PATCH',path,headers,params,progress);},'delete':function(path,headers={},params={},progress=null){return call('DELETE',path,headers,params,progress);},'addGlobalParam':addGlobalParam,'addGlobalHeader':addGlobalHeader}}(window.document);let analytics={create:function(id,source,activity,url){return http.post('/analytics',{'content-type':'application/json'},{id:id,source:source,activity:activity,url:url,version:env.VERSION,setup:env.SETUP});},};return{analytics:analytics,};},true);})(window);(function(window){"use strict";window.ls.container.set('console',function(window){var client=new Appwrite.Client();var endpoint=window.location.origin+'/v1';client.setEndpoint(endpoint).setProject('console').setLocale(APP_ENV.LOCALE);return{client:client,account:new Appwrite.Account(client),avatars:new Appwrite.Avatars(client),databases:new Appwrite.Databases(client),functions:new Appwrite.Functions(client),health:new Appwrite.Health(client),locale:new Appwrite.Locale(client),projects:new Appwrite.Projects(client),storage:new Appwrite.Storage(client),teams:new Appwrite.Teams(client),users:new Appwrite.Users(client)}},true);})(window);(function(window){"use strict";window.ls.container.set('date',function(){function format(format,datetime){if(!datetime){return null;}
|
||||
return new Intl.DateTimeFormat('en-US',{timeZone:'UTC',hourCycle:'h24',...format}).format(new Date(datetime));}
|
||||
return{format:format,}}(),true);})(window);(function(window){"use strict";window.ls.container.set('env',function(){return APP_ENV;},true);})(window);(function(window){"use strict";window.ls.container.set('form',function(){function cast(value,from,to,){if(value&&Array.isArray(value)&&to!=='array'){value=value.map(element=>cast(element,from,to));return value;}
|
||||
switch(to){case'int':case'integer':value=parseInt(value);break;case'numeric':value=Number(value);break;case'float':value=parseFloat(value);break;case'string':value=value.toString();if(value.length===0){value=null;}
|
||||
break;case'json':value=(value)?JSON.parse(value):[];break;case'array':value=(value&&value.constructor&&value.constructor===Array)?value:[value];break;case'array-empty':value=[];break;case'bool':case'boolean':value=(value==='false')?false:value;value=!!value;break;}
|
||||
break;case'json':value=(value)?JSON.parse(value):[];break;case'array':if(value&&value.constructor&&value.constructor===Array){break;}
|
||||
if(from==='csv'){if(value.length===0){value=[];}else{value=value.split(',');}}else{value=[value];}
|
||||
break;case'array-empty':value=[];break;case'bool':case'boolean':value=(value==='false')?false:value;value=!!value;break;}
|
||||
return value;}
|
||||
function toJson(element,json){json=json||{};let name=element.getAttribute('name');let type=element.getAttribute('type');let castTo=element.getAttribute('data-cast-to');let ref=json;if(name&&'FORM'!==element.tagName){if(name.startsWith('[')){let splitName=name.split('.');if(splitName.length>1&&splitName[0].endsWith(']')){name=splitName[splitName.length-1];}}
|
||||
function toJson(element,json){json=json||{};let name=element.getAttribute('name');let type=element.getAttribute('type');let castTo=element.getAttribute('data-cast-to');let castFrom=element.getAttribute('data-cast-from');let ref=json;if(name&&'FORM'!==element.tagName){if(name.startsWith('[')){let splitName=name.split('.');if(splitName.length>1&&splitName[0].endsWith(']')){name=splitName[splitName.length-1];}}
|
||||
if('FIELDSET'===element.tagName){if(castTo==='object'){if(json[name]===undefined){json[name]={};}
|
||||
ref=json[name];}
|
||||
else{if(!Array.isArray(json[name])){json[name]=[];}
|
||||
|
|
@ -4077,7 +3987,7 @@ else if('file'===type){json[name]=element.files[0];}
|
|||
else if(undefined!==element.value){if((json[name]!==undefined)&&(!Array.isArray(json[name]))){json[name]=[json[name]];}
|
||||
if(Array.isArray(json[name])){json[name].push(element.value);}
|
||||
else{json[name]=element.value;}}
|
||||
json[name]=cast(json[name],castTo);}}
|
||||
json[name]=cast(json[name],castFrom,castTo);}}
|
||||
for(let i=0;i<element.children.length;i++){if(Array.isArray(ref)){ref.push({});toJson(element.children[i],ref[ref.length]);}
|
||||
else{toJson(element.children[i],ref);}}
|
||||
return json;}
|
||||
|
|
@ -4090,7 +4000,7 @@ return false;};return{isRTL:isRTL,};},true);})(window);(function(window){"use st
|
|||
let size=element.dataset["size"]||80;let name=$value.name||$value||"";name=(typeof name!=='string')?'--':name;return def="/v1/avatars/initials?project=console"+"&name="+
|
||||
encodeURIComponent(name)+"&width="+
|
||||
size+"&height="+
|
||||
size;}).add("selectedCollection",function($value,router){return $value===router.params.collectionId?"selected":"";}).add("selectedDocument",function($value,router){return $value===router.params.documentId?"selected":"";}).add("localeString",function($value){$value=parseInt($value);return!Number.isNaN($value)?$value.toLocaleString():"";}).add("date",function($value,date){return $value?date.format("Y-m-d",$value):"";}).add("dateTime",function($value,date){return $value?date.format("Y-m-d H:i",$value):"";}).add("dateText",function($value,date){return $value?date.format("d M Y",$value):"";}).add("timeSince",function($value){$value=$value*1000;let seconds=Math.floor((Date.now()-$value)/1000);let unit="second";let direction="ago";if(seconds<0){seconds=-seconds;direction="from now";}
|
||||
size;}).add("selectedCollection",function($value,router){return $value===router.params.collectionId?"selected":"";}).add("selectedDocument",function($value,router){return $value===router.params.documentId?"selected":"";}).add("localeString",function($value){$value=parseInt($value);return!Number.isNaN($value)?$value.toLocaleString():"";}).add("dateTime",function($value,date){return $value?date.format({year:'numeric',month:'2-digit',day:'2-digit',hour:'2-digit',minute:'2-digit'},$value):"";}).add("date",function($value,date){return $value?date.format({year:'numeric',month:'short',day:'2-digit',},$value):"";}).add("timeSince",function($value){$value=new Date($value).getTime();let now=new Date();now.setMinutes(now.getMinutes()+now.getTimezoneOffset());let timestamp=new Date(now.toISOString()).getTime();let seconds=Math.floor((timestamp-$value)/1000);let unit="second";let direction="ago";if(seconds<0){seconds=-seconds;direction="from now";}
|
||||
let value=seconds;if(seconds>=31536000){value=Math.floor(seconds/31536000);unit="year";}
|
||||
else if(seconds>=86400){value=Math.floor(seconds/86400);unit="day";}
|
||||
else if(seconds>=3600){value=Math.floor(seconds/3600);unit="hour";}
|
||||
|
|
@ -4128,11 +4038,10 @@ if(forcePlaces!==false){rounded=Number(rounded).toFixed(forcePlaces);}
|
|||
return rounded+abbr;}
|
||||
window.ls.container.get("view").add({selector:"data-acl",controller:function(element,document,router,alerts){document.body.classList.remove("console");document.body.classList.remove("home");document.body.classList.add(router.getCurrent().view.scope);if(!router.getCurrent().view.project){document.body.classList.add("hide-nav");document.body.classList.remove("show-nav");}else{document.body.classList.add("show-nav");document.body.classList.remove("hide-nav");}
|
||||
if("/console"===router.getCurrent().path){document.body.classList.add("index");}else{document.body.classList.remove("index");}}}).add({selector:"data-prism",controller:function(window,document,element,alerts){Prism.highlightElement(element);let copy=document.createElement("i");copy.className="icon-docs copy";copy.title="Copy to Clipboard";copy.textContent="Click Here to Copy";copy.addEventListener("click",function(){window.getSelection().removeAllRanges();let range=document.createRange();range.selectNode(element);window.getSelection().addRange(range);try{document.execCommand("copy");alerts.add({text:"Copied to clipboard",class:""},3000);}catch(err){alerts.add({text:"Failed to copy text ",class:"error"},3000);}
|
||||
window.getSelection().removeAllRanges();});element.parentNode.parentNode.appendChild(copy);}});(function(window){document.addEventListener('alpine:init',()=>{Alpine.store('uploader',{_files:[],files(){return(this._files??[]).filter((file)=>!file.cancelled);},isOpen:true,init(){window.addEventListener('beforeunload',(event)=>{if(this.hasOngoingUploads()){let confirmationMessage="There are incomplete uploads, are you sure you want to leave?";event.returnValue=confirmationMessage;return confirmationMessage;}});},cancelAll(){if(this.hasOngoingUploads()?confirm("Are you sure? This will cancel and remove any ongoing uploads?"):true){this._files.forEach(file=>{if(file.completed||file.failed){this.removeFile(file.id);}else{this.updateFile(file.id,{cancelled:true});}});}},hasOngoingUploads(){let ongoing=false;this._files.some((file)=>{if(!file.completed&&!file.failed){ongoing=true;return;}});return ongoing;},toggle(){this.isOpen=!this.isOpen;},addFile(file){this._files.push(file);},updateFile(id,file){this._files=this._files.map((oldFile)=>id==oldFile.id?{...oldFile,...file}:oldFile);},removeFile(id){const file=this.getFile(id)??{};if(file.completed||file.failed){this._files=this._files.filter((file)=>file.id!==id);}else{if(confirm("Are you sure you want to cancel the upload?")){this.updateFile(id,{cancelled:true});}}},getFile(id){return this._files.find((file)=>file.id===id);},async uploadFile(target){const formData=new FormData(target);const sdk=window.ls.container.get('sdk');const bucketId=formData.get('bucketId');const file=formData.get('file');const fileId=formData.get('fileId');let id=fileId==='unique()'?performance.now():fileId;let read=formData.get('read');if(!file||!fileId){return;}
|
||||
if(read){read=JSON.parse(read);}
|
||||
let write=formData.get('write');if(write){write=JSON.parse(write);}
|
||||
window.getSelection().removeAllRanges();});element.parentNode.parentNode.appendChild(copy);}});(function(window){document.addEventListener('alpine:init',()=>{Alpine.store('uploader',{_files:[],files(){return(this._files??[]).filter((file)=>!file.cancelled);},isOpen:true,init(){window.addEventListener('beforeunload',(event)=>{if(this.hasOngoingUploads()){let confirmationMessage="There are incomplete uploads, are you sure you want to leave?";event.returnValue=confirmationMessage;return confirmationMessage;}});},cancelAll(){if(this.hasOngoingUploads()?confirm("Are you sure? This will cancel and remove any ongoing uploads?"):true){this._files.forEach(file=>{if(file.completed||file.failed){this.removeFile(file.id);}else{this.updateFile(file.id,{cancelled:true});}});}},hasOngoingUploads(){let ongoing=false;this._files.some((file)=>{if(!file.completed&&!file.failed){ongoing=true;return;}});return ongoing;},toggle(){this.isOpen=!this.isOpen;},addFile(file){this._files.push(file);},updateFile(id,file){this._files=this._files.map((oldFile)=>id==oldFile.id?{...oldFile,...file}:oldFile);},removeFile(id){const file=this.getFile(id)??{};if(file.completed||file.failed){this._files=this._files.filter((file)=>file.id!==id);}else{if(confirm("Are you sure you want to cancel the upload?")){this.updateFile(id,{cancelled:true});}}},getFile(id){return this._files.find((file)=>file.id===id);},async uploadFile(target){const formData=new FormData(target);const sdk=window.ls.container.get('sdk');const bucketId=formData.get('bucketId');const file=formData.get('file');const fileId=formData.get('fileId');let id=fileId==='unique()'?performance.now():fileId;if(!file||!fileId){return;}
|
||||
let permissions=formData.get('permissions');if(permissions){permissions=permissions.split(',');}
|
||||
if(this.getFile(id)){this.updateFile(id,{name:file.name,completed:false,failed:false,cancelled:false,error:"",});}else{this.addFile({id:id,name:file.name,progress:0,completed:false,failed:false,cancelled:false,error:"",});}
|
||||
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;}
|
||||
target.reset();try{const response=await sdk.storage.createFile(bucketId,fileId,file,permissions,(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){document.addEventListener('alpine:init',()=>{Alpine.data('events',()=>({events:new Set(),selected:null,action:null,type:null,subType:null,subSubType:null,resource:null,resourceName:'',subResource:null,subResourceName:'',subSubResource:null,subSubResourceName:'',hasResource:false,hasSubResource:false,hasSubSubResource:false,attribute:null,hasAttribute:false,attributes:[],load(events){this.events=new Set(events);},reset(){this.hasResource=this.hasSubResource=this.hasSubSubResource=this.hasAttribute=false;this.type=this.subType=this.subResource=this.resource=this.attribute=this.selected=this.action=null;},setEvent(){this.hasResource=this.hasSubResource=this.hasSubSubResource=this.hasAttribute=this.action=false;if(!this.selected){this.reset();return;}
|
||||
let[type,action]=this.selected.split('.');switch(type){case'users':if(action==='update'){this.hasAttribute=true;this.attributes=['email','name','password','status','prefs']}
|
||||
|
|
@ -4142,7 +4051,14 @@ this.action=action;},showModal(modal){document.documentElement.classList.add("mo
|
|||
if(this.hasSubSubResource){event+=`.${this.subSubType}.${this.subSubResource ? this.subSubResource : '*'}`;}
|
||||
if(this.action){event+=`.${this.action}`;}
|
||||
if(this.attribute){event+=`.${this.attribute}`;}
|
||||
this.events.add(event);this.reset();},removeEvent(value){this.events.delete(value);}}));});})(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();}
|
||||
this.events.add(event);this.reset();},removeEvent(value){this.events.delete(value);}}));});})(window);(function(window){document.addEventListener('alpine:init',()=>{Alpine.data('permissionsMatrix',()=>({permissions:[],rawPermissions:[],load(permissions){if(permissions===undefined){return;}
|
||||
this.rawPermissions=permissions;permissions.map(p=>{let{type,role}=this.parsePermission(p);type=this.parseInputPermission(type);let index=-1;let existing=this.permissions.find((p,idx)=>{if(p.role===role){index=idx;return true;}})
|
||||
if(existing===undefined){this.permissions.push({role,[type]:true,});}
|
||||
if(index!==-1){existing[type]=true;this.permissions[index]=existing;}});},addPermission(formId,role,permissions){if(!document.getElementById(formId).reportValidity()){return;}
|
||||
Object.entries(permissions).forEach(entry=>{let[type,enabled]=entry;type=this.parseOutputPermission(type);if(enabled){this.rawPermissions.push(this.buildPermission(type,role));}});this.permissions.push({role,...permissions,});this.reset();},updatePermission(index){setTimeout(()=>{const permission=this.permissions[index];Object.keys(permission).forEach(key=>{if(key==='role'){return;}
|
||||
const parsedKey=this.parseOutputPermission(key);const permissionString=this.buildPermission(parsedKey,permission.role);if(permission[key]){if(!this.rawPermissions.includes(permissionString)){this.rawPermissions.push(permissionString);}}else{this.rawPermissions=this.rawPermissions.filter(p=>{return!p.includes(permissionString);});}});});},removePermission(index){let row=this.permissions.splice(index,1);if(row.length===1){this.rawPermissions=this.rawPermissions.filter(p=>!p.includes(row[0].role));}},parsePermission(permission){let parts=permission.split('(');let type=parts[0];let role=parts[1].replace(')','').replace(' ','').replaceAll('"','');return{type,role};},buildPermission(type,role){return`${type}("${role}")`},parseInputPermission(key){if(key==='delete'){return'xdelete';}
|
||||
return key;},parseOutputPermission(key){if(key==='xdelete'){return'delete';}
|
||||
return key;}}));Alpine.data('permissionsRow',()=>({role:'',read:false,create:false,update:false,xdelete:false,reset(){this.role='';this.read=this.create=this.update=this.xdelete=false;}}));});})(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"},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];}
|
||||
|
|
@ -4188,9 +4104,9 @@ button.addEventListener("click",function(){var clone=document.createElement(elem
|
|||
clone.innerHTML=template;clone.className=element.className;var input=clone.querySelector("input, select, textarea");view.render(clone);if(debug){console.log('Debug: clone: ',clone);console.log('Debug: target: ',target);}
|
||||
if(target){target.appendChild(clone);}else{button.parentNode.insertBefore(clone,button);}
|
||||
if(input){input.focus();}
|
||||
Array.prototype.slice.call(clone.querySelectorAll("[data-remove]")).map(function(obj){obj.addEventListener("click",function(){clone.parentNode.removeChild(clone);obj.scrollIntoView({behavior:"smooth"});});});Array.prototype.slice.call(clone.querySelectorAll("[data-up]")).map(function(obj){obj.addEventListener("click",function(){if(clone.previousElementSibling){clone.parentNode.insertBefore(clone,clone.previousElementSibling);obj.scrollIntoView({behavior:"smooth"});}});});Array.prototype.slice.call(clone.querySelectorAll("[data-down]")).map(function(obj){obj.addEventListener("click",function(){if(clone.nextElementSibling){clone.parentNode.insertBefore(clone.nextElementSibling,clone);obj.scrollIntoView({behavior:"smooth"});}});});});element.parentNode.insertBefore(button,element.nextSibling);element.parentNode.removeChild(element);button.form.addEventListener('reset',function(event){target.innerHTML='';if(first){button.click();}});if(first){button.click();}}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-add",repeat:false,controller:function(element,view,container,document){for(var i=0;i<element.children.length;i++){let button=document.createElement("button");let template=element.children[i].cloneNode(true);let as=element.getAttribute('data-ls-as');let counter=0;button.type="button";button.innerText="Add";button.classList.add("reverse");button.classList.add("margin-end-small");button.addEventListener('click',function(){container.addNamespace(as,'new-'+counter++);console.log(container.namespaces,container.get(as),as);container.set(as,null,true,true);let child=template.cloneNode(true);view.render(child);element.appendChild(child);element.style.visibility='visible';let inputs=child.querySelectorAll('input,textarea');for(let index=0;index<inputs.length;++index){if(inputs[index].type!=='hidden'){inputs[index].focus();break;}}});element.after(button);}}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-chart",controller:function(element,container,date,document){let wrapper=document.createElement("div");let child=document.createElement("canvas");let sources=element.getAttribute('data-forms-chart');let width=element.getAttribute('data-width')||500;let height=element.getAttribute('data-height')||175;let showXAxis=element.getAttribute('data-show-x-axis')||false;let showYAxis=element.getAttribute('data-show-y-axis')||false;let colors=(element.getAttribute('data-colors')||'blue,green,orange,red').split(',');let themes={'blue':'#29b5d9','green':'#4eb55b','orange':'#fba233','red':'#dc3232','create':'#00b680','read':'#009cde','update':'#696fd7','delete':'#da5d95',};let range={'24h':'H:i','7d':'d F Y','30d':'d F Y','90d':'d F Y'}
|
||||
Array.prototype.slice.call(clone.querySelectorAll("[data-remove]")).map(function(obj){obj.addEventListener("click",function(){clone.parentNode.removeChild(clone);obj.scrollIntoView({behavior:"smooth"});});});Array.prototype.slice.call(clone.querySelectorAll("[data-up]")).map(function(obj){obj.addEventListener("click",function(){if(clone.previousElementSibling){clone.parentNode.insertBefore(clone,clone.previousElementSibling);obj.scrollIntoView({behavior:"smooth"});}});});Array.prototype.slice.call(clone.querySelectorAll("[data-down]")).map(function(obj){obj.addEventListener("click",function(){if(clone.nextElementSibling){clone.parentNode.insertBefore(clone.nextElementSibling,clone);obj.scrollIntoView({behavior:"smooth"});}});});});element.parentNode.insertBefore(button,element.nextSibling);element.parentNode.removeChild(element);button.form.addEventListener('reset',function(event){target.innerHTML='';if(first){button.click();}});if(first){button.click();}}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-add",repeat:false,controller:function(element,view,container,document){for(var i=0;i<element.children.length;i++){let button=document.createElement("button");let template=element.children[i].cloneNode(true);let as=element.getAttribute('data-ls-as');let counter=0;button.type="button";button.innerText="Add";button.classList.add("reverse");button.classList.add("margin-end-small");button.addEventListener('click',function(){container.addNamespace(as,'new-'+counter++);console.log(container.namespaces,container.get(as),as);container.set(as,null,true,true);let child=template.cloneNode(true);view.render(child);element.appendChild(child);element.style.visibility='visible';let inputs=child.querySelectorAll('input,textarea');for(let index=0;index<inputs.length;++index){if(inputs[index].type!=='hidden'){inputs[index].focus();break;}}});element.after(button);}}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-chart",controller:function(element,container,date,document){let wrapper=document.createElement("div");let child=document.createElement("canvas");let sources=element.getAttribute('data-forms-chart');let width=element.getAttribute('data-width')||500;let height=element.getAttribute('data-height')||175;let showXAxis=element.getAttribute('data-show-x-axis')||false;let showYAxis=element.getAttribute('data-show-y-axis')||false;let colors=(element.getAttribute('data-colors')||'blue,green,orange,red').split(',');let themes={'blue':'#29b5d9','green':'#4eb55b','orange':'#fba233','red':'#dc3232','create':'#00b680','read':'#009cde','update':'#696fd7','delete':'#da5d95',};let range={'24h':{hour:'2-digit',minute:'2-digit'},'7d':{year:'numeric',month:'short',day:'2-digit',},'30d':{year:'numeric',month:'short',day:'2-digit',},'90d':{year:'numeric',month:'short',day:'2-digit',}}
|
||||
let ticksCount=5;element.parentNode.insertBefore(wrapper,element.nextSibling);wrapper.classList.add('content');child.width=width;child.height=height;sources=sources.split(',');wrapper.appendChild(child);let chart=null;let check=function(){let config={type:"line",data:{labels:[],datasets:[]},options:{animation:{duration:0},responsive:true,hover:{mode:"nearest",intersect:false},scales:{x:{display:showXAxis},y:{display:showYAxis,min:0,ticks:{count:ticksCount,fontColor:"#8f8f8f"},}},plugins:{title:{display:false,text:"Stats"},legend:{display:false},tooltip:{mode:"index",intersect:false,caretPadding:0},}}};let highest=0;for(let i=0;i<sources.length;i++){let label=sources[i].substring(0,sources[i].indexOf('='));let path=sources[i].substring(sources[i].indexOf('=')+1);let usage=container.get('usage');let data=usage[path];let value=JSON.parse(element.value);config.data.labels[i]=label;config.data.datasets[i]={};config.data.datasets[i].label=label;config.data.datasets[i].borderColor=themes[colors[i]];config.data.datasets[i].backgroundColor=themes[colors[i]]+'36';config.data.datasets[i].borderWidth=2;config.data.datasets[i].data=[0,0,0,0,0,0,0];config.data.datasets[i].fill=true;if(!data){return;}
|
||||
let dateFormat=(value.range&&range[value.range])?range[value.range]:'d F Y';for(let x=0;x<data.length;x++){if(data[x].value>highest){highest=data[x].value;}
|
||||
let dateFormat=(value.range&&range[value.range])?range[value.range]:{year:'numeric',month:'short',day:'2-digit',};for(let x=0;x<data.length;x++){if(data[x].value>highest){highest=data[x].value;}
|
||||
config.data.datasets[i].data[x]=data[x].value;config.data.labels[x]=date.format(dateFormat,data[x].date);}}
|
||||
if(highest==0){config.options.scales.y.ticks.stepSize=1;config.options.scales.y.max=ticksCount;}else{highest=Math.ceil(highest/ticksCount)*ticksCount;config.options.scales.y.ticks.stepSize=highest/ticksCount;config.options.scales.y.max=highest;}
|
||||
if(chart){chart.destroy();}
|
||||
|
|
@ -4263,7 +4179,8 @@ var file=document.createElement("li");var image=document.createElement("img");im
|
|||
result.bucketId+"/files/"+
|
||||
result.fileId+"/preview?width="+
|
||||
previewWidth+"&height="+
|
||||
previewHeight+"&project="+project+"&mode=admin";image.alt=previewAlt;file.className="file avatar";file.tabIndex=0;file.appendChild(image);preview.appendChild(file);var remove=(function(result){return function(event){render(result.$id);element.value='';};})(result);file.addEventListener("click",remove);file.addEventListener("keypress",remove);element.value=JSON.stringify(result);};input.addEventListener("change",function(){var message=alerts.add({text:labelLoading,class:""},0);var files=input.files;var read=JSON.parse(expression.parse(element.dataset["read"]||"[]"));var write=JSON.parse(expression.parse(element.dataset["write"]||"[]"));sdk.storage.createFile('default','unique()',files[0],read,write).then(function(response){onComplete(message);render({bucketId:response.bucketId,fileId:response.$id});},function(error){alerts.add({text:"An error occurred!",class:""},3000);onComplete(message);});input.disabled=true;});element.addEventListener("change",function(){if(!element.value){return;}
|
||||
previewHeight+"&project="+project+"&mode=admin";image.alt=previewAlt;file.className="file avatar";file.tabIndex=0;file.appendChild(image);preview.appendChild(file);var remove=(function(result){return function(event){render(result.$id);element.value='';};})(result);file.addEventListener("click",remove);file.addEventListener("keypress",remove);element.value=JSON.stringify(result);};input.addEventListener("change",function(){var message=alerts.add({text:labelLoading,class:""},0);var files=input.files;var permissions=JSON.parse(expression.parse(element.dataset["permissions"]||"[]"))
|
||||
sdk.storage.createFile('default','unique()',files[0],permissions).then(function(response){onComplete(message);render({bucketId:response.bucketId,fileId:response.$id});},function(error){alerts.add({text:"An error occurred!",class:""},3000);onComplete(message);});input.disabled=true;});element.addEventListener("change",function(){if(!element.value){return;}
|
||||
render(element.value);wrapper.scrollIntoView();});upload.addEventListener("keypress",function(){input.click();});element.parentNode.insertBefore(wrapper,element);wrapper.appendChild(preview);wrapper.appendChild(progress);wrapper.appendChild(upload);upload.appendChild(input);render(output);if(searchButton){let searchOpen=document.createElement("button");searchOpen.type='button';searchOpen.innerHTML='<i class="icon icon-search"></i> Search';searchOpen.classList.add('reverse');let path=container.scope(searchButton);searchOpen.addEventListener('click',function(){search.selected=element.value;search.path=path;document.dispatchEvent(new CustomEvent("open-file-search",{bubbles:false,cancelable:true}));});wrapper.appendChild(searchOpen);}}});})(window);(function(window){window.ls.container.get("view").add({selector:"data-cookies",controller:function(element,alerts,cookie,env){if(!cookie.get("cookie-alert")){let text=element.dataset["cookies"]||"";alerts.add({text:text,class:"cookie-alert",link:env.HOME+"/policy/cookies",label:'Learn More',callback:function(){cookie.set("cookie-alert","true",365*10);}},0);}}});})(window);(function(window){"use strict";window.ls.view.add({selector:'data-general-copy',repeat:false,controller:function(document,element,alerts){let button=document.createElement("i");button.type="button";button.title="Copy to Clipboard";button.className=element.getAttribute("data-class")||"icon-docs note copy";button.style.cursor="pointer";element.parentNode.insertBefore(button,element.nextSibling);let copy=function(event){window.getSelection().removeAllRanges();let range=document.createRange();range.selectNode(element);window.getSelection().addRange(range);try{document.execCommand("copy");alerts.add({text:"Copied to clipboard",class:""},3000);}catch(err){alerts.add({text:"Failed to copy text ",class:"error"},3000);}
|
||||
window.getSelection().removeAllRanges();};button.addEventListener("click",copy);}});})(window);(function(window){window.ls.container.get("view").add({selector:"data-page-title",repeat:true,controller:function(element,document,expression){document.title=expression.parse(element.getAttribute("data-page-title"))||document.title;}});})(window);(function(window){"use strict";window.ls.view.add({selector:'data-general-scroll-to',repeat:false,controller:function(element,window){let button=window.document.createElement('button');button.className='scroll-to icon-up-dir';button.alt='Back To Top';button.title='Back To Top';button.addEventListener('click',function(){element.scrollIntoView(true,{behavior:'smooth'});button.blur();},false);element.appendChild(button);}});})(window);(function(window){"use strict";window.ls.view.add({selector:'data-general-scroll-direction',repeat:false,controller:function(element,window){let position=0;let check=function(){let direction=window.document.documentElement.scrollTop;if(direction>position){element.classList.remove('scroll-to-top')
|
||||
element.classList.add('scroll-to-bottom')}
|
||||
|
|
|
|||
84
public/dist/scripts/app-dep.js
vendored
84
public/dist/scripts/app-dep.js
vendored
|
|
@ -9,9 +9,9 @@ static flatten(data,prefix=''){let output={};for(const key in data){let value=da
|
|||
else{output[finalKey]=value;}}
|
||||
return output;}}
|
||||
Service.CHUNK_SIZE=5*1024*1024;class Query{}
|
||||
Query.equal=(attribute,value)=>Query.addQuery(attribute,'equal',value);Query.notEqual=(attribute,value)=>Query.addQuery(attribute,'notEqual',value);Query.lesser=(attribute,value)=>Query.addQuery(attribute,'lesser',value);Query.lesserEqual=(attribute,value)=>Query.addQuery(attribute,'lesserEqual',value);Query.greater=(attribute,value)=>Query.addQuery(attribute,'greater',value);Query.greaterEqual=(attribute,value)=>Query.addQuery(attribute,'greaterEqual',value);Query.search=(attribute,value)=>Query.addQuery(attribute,'search',value);Query.addQuery=(attribute,oper,value)=>value instanceof Array?`${attribute}.${oper}(${value
|
||||
Query.equal=(attribute,value)=>Query.addQuery(attribute,"equal",value);Query.notEqual=(attribute,value)=>Query.addQuery(attribute,"notEqual",value);Query.lessThan=(attribute,value)=>Query.addQuery(attribute,"lessThan",value);Query.lessThanEqual=(attribute,value)=>Query.addQuery(attribute,"lessThanEqual",value);Query.greaterThan=(attribute,value)=>Query.addQuery(attribute,"greaterThan",value);Query.greaterThanEqual=(attribute,value)=>Query.addQuery(attribute,"greaterThanEqual",value);Query.search=(attribute,value)=>Query.addQuery(attribute,"search",value);Query.orderDesc=(attribute)=>`orderDesc("${attribute}")`;Query.orderAsc=(attribute)=>`orderAsc("${attribute}")`;Query.cursorAfter=(documentId)=>`cursorAfter("${documentId}")`;Query.cursorBefore=(documentId)=>`cursorBefore("${documentId}")`;Query.limit=(limit)=>`limit(${limit})`;Query.offset=(offset)=>`offset(${offset})`;Query.addQuery=(attribute,method,value)=>value instanceof Array?`${method}("${attribute}", [${value
|
||||
.map((v) => Query.parseValues(v))
|
||||
.join(',')})`:`${attribute}.${oper}(${Query.parseValues(value)})`;Query.parseValues=(value)=>typeof value==='string'||value instanceof String?`"${value}"`:`${value}`;class AppwriteException extends Error{constructor(message,code=0,type='',response=''){super(message);this.name='AppwriteException';this.message=message;this.code=code;this.type=type;this.response=response;}}
|
||||
.join(",")}])`:`${method}("${attribute}", [${Query.parseValues(value)}])`;Query.parseValues=(value)=>typeof value==="string"||value instanceof String?`"${value}"`:`${value}`;class AppwriteException extends Error{constructor(message,code=0,type='',response=''){super(message);this.name='AppwriteException';this.message=message;this.code=code;this.type=type;this.response=response;}}
|
||||
class Client{constructor(){this.config={endpoint:'https://HOSTNAME/v1',endpointRealtime:'',project:'',key:'',jwt:'',locale:'',mode:'',};this.headers={'x-sdk-version':'appwrite:web:6.0.0','X-Appwrite-Response-Format':'0.15.0',};this.realtime={socket:undefined,timeout:undefined,url:'',channels:new Set(),subscriptions:new Map(),subscriptionsCounter:0,reconnect:true,reconnectAttempts:0,lastMessage:undefined,connect:()=>{clearTimeout(this.realtime.timeout);this.realtime.timeout=window===null||window===void 0?void 0:window.setTimeout(()=>{this.realtime.createSocket();},50);},getTimeout:()=>{switch(true){case this.realtime.reconnectAttempts<5:return 1000;case this.realtime.reconnectAttempts<15:return 5000;case this.realtime.reconnectAttempts<100:return 10000;default:return 60000;}},createSocket:()=>{var _a,_b;if(this.realtime.channels.size<1)
|
||||
return;const channels=new URLSearchParams();channels.set('project',this.config.project);this.realtime.channels.forEach(channel=>{channels.append('channels[]',channel);});const url=this.config.endpointRealtime+'/realtime?'+channels.toString();if(url!==this.realtime.url||!this.realtime.socket||((_a=this.realtime.socket)===null||_a===void 0?void 0:_a.readyState)>WebSocket.OPEN){if(this.realtime.socket&&((_b=this.realtime.socket)===null||_b===void 0?void 0:_b.readyState)<WebSocket.CLOSING){this.realtime.reconnect=false;this.realtime.socket.close();}
|
||||
this.realtime.url=url;this.realtime.socket=new WebSocket(url);this.realtime.socket.addEventListener('message',this.realtime.onMessage);this.realtime.socket.addEventListener('open',_event=>{this.realtime.reconnectAttempts=0;});this.realtime.socket.addEventListener('close',event=>{var _a,_b,_c;if(!this.realtime.reconnect||(((_b=(_a=this.realtime)===null||_a===void 0?void 0:_a.lastMessage)===null||_b===void 0?void 0:_b.type)==='error'&&((_c=this.realtime)===null||_c===void 0?void 0:_c.lastMessage.data).code===1008)){this.realtime.reconnect=true;return;}
|
||||
|
|
@ -220,29 +220,26 @@ if(typeof cursor!=='undefined'){payload['cursor']=cursor;}
|
|||
if(typeof cursorDirection!=='undefined'){payload['cursorDirection']=cursorDirection;}
|
||||
if(typeof orderType!=='undefined'){payload['orderType']=orderType;}
|
||||
const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('get',uri,{'content-type':'application/json',},payload);});}
|
||||
createCollection(databaseId,collectionId,name,permission,read,write){return __awaiter(this,void 0,void 0,function*(){if(typeof databaseId==='undefined'){throw new AppwriteException('Missing required parameter: "databaseId"');}
|
||||
createCollection(databaseId,collectionId,name,permissions,documentSecurity){return __awaiter(this,void 0,void 0,function*(){if(typeof databaseId==='undefined'){throw new AppwriteException('Missing required parameter: "databaseId"');}
|
||||
if(typeof collectionId==='undefined'){throw new AppwriteException('Missing required parameter: "collectionId"');}
|
||||
if(typeof name==='undefined'){throw new AppwriteException('Missing required parameter: "name"');}
|
||||
if(typeof permission==='undefined'){throw new AppwriteException('Missing required parameter: "permission"');}
|
||||
if(typeof read==='undefined'){throw new AppwriteException('Missing required parameter: "read"');}
|
||||
if(typeof write==='undefined'){throw new AppwriteException('Missing required parameter: "write"');}
|
||||
if(typeof permissions==='undefined'){throw new AppwriteException('Missing required parameter: "permissions"');}
|
||||
if(typeof documentSecurity==='undefined'){throw new AppwriteException('Missing required parameter: "documentSecurity"');}
|
||||
let path='/databases/{databaseId}/collections'.replace('{databaseId}',databaseId);let payload={};if(typeof collectionId!=='undefined'){payload['collectionId']=collectionId;}
|
||||
if(typeof name!=='undefined'){payload['name']=name;}
|
||||
if(typeof permission!=='undefined'){payload['permission']=permission;}
|
||||
if(typeof read!=='undefined'){payload['read']=read;}
|
||||
if(typeof write!=='undefined'){payload['write']=write;}
|
||||
if(typeof permissions!=='undefined'){payload['permissions']=permissions;}
|
||||
if(typeof documentSecurity!=='undefined'){payload['documentSecurity']=documentSecurity;}
|
||||
const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('post',uri,{'content-type':'application/json',},payload);});}
|
||||
getCollection(databaseId,collectionId){return __awaiter(this,void 0,void 0,function*(){if(typeof databaseId==='undefined'){throw new AppwriteException('Missing required parameter: "databaseId"');}
|
||||
if(typeof collectionId==='undefined'){throw new AppwriteException('Missing required parameter: "collectionId"');}
|
||||
let path='/databases/{databaseId}/collections/{collectionId}'.replace('{databaseId}',databaseId).replace('{collectionId}',collectionId);let payload={};const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('get',uri,{'content-type':'application/json',},payload);});}
|
||||
updateCollection(databaseId,collectionId,name,permission,read,write,enabled){return __awaiter(this,void 0,void 0,function*(){if(typeof databaseId==='undefined'){throw new AppwriteException('Missing required parameter: "databaseId"');}
|
||||
updateCollection(databaseId,collectionId,name,documentSecurity,permissions,enabled){return __awaiter(this,void 0,void 0,function*(){if(typeof databaseId==='undefined'){throw new AppwriteException('Missing required parameter: "databaseId"');}
|
||||
if(typeof collectionId==='undefined'){throw new AppwriteException('Missing required parameter: "collectionId"');}
|
||||
if(typeof name==='undefined'){throw new AppwriteException('Missing required parameter: "name"');}
|
||||
if(typeof permission==='undefined'){throw new AppwriteException('Missing required parameter: "permission"');}
|
||||
if(typeof documentSecurity==='undefined'){throw new AppwriteException('Missing required parameter: "documentSecurity"');}
|
||||
let path='/databases/{databaseId}/collections/{collectionId}'.replace('{databaseId}',databaseId).replace('{collectionId}',collectionId);let payload={};if(typeof name!=='undefined'){payload['name']=name;}
|
||||
if(typeof permission!=='undefined'){payload['permission']=permission;}
|
||||
if(typeof read!=='undefined'){payload['read']=read;}
|
||||
if(typeof write!=='undefined'){payload['write']=write;}
|
||||
if(typeof permissions!=='undefined'){payload['permissions']=permissions;}
|
||||
if(typeof documentSecurity!=='undefined'){payload['documentSecurity']=documentSecurity;}
|
||||
if(typeof enabled!=='undefined'){payload['enabled']=enabled;}
|
||||
const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('put',uri,{'content-type':'application/json',},payload);});}
|
||||
deleteCollection(databaseId,collectionId){return __awaiter(this,void 0,void 0,function*(){if(typeof databaseId==='undefined'){throw new AppwriteException('Missing required parameter: "databaseId"');}
|
||||
|
|
@ -260,6 +257,15 @@ if(typeof required!=='undefined'){payload['required']=required;}
|
|||
if(typeof xdefault!=='undefined'){payload['default']=xdefault;}
|
||||
if(typeof array!=='undefined'){payload['array']=array;}
|
||||
const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('post',uri,{'content-type':'application/json',},payload);});}
|
||||
createDatetimeAttribute(databaseId,collectionId,key,required,xdefault,array){return __awaiter(this,void 0,void 0,function*(){if(typeof databaseId==='undefined'){throw new AppwriteException('Missing required parameter: "databaseId"');}
|
||||
if(typeof collectionId==='undefined'){throw new AppwriteException('Missing required parameter: "collectionId"');}
|
||||
if(typeof key==='undefined'){throw new AppwriteException('Missing required parameter: "key"');}
|
||||
if(typeof required==='undefined'){throw new AppwriteException('Missing required parameter: "required"');}
|
||||
let path='/databases/{databaseId}/collections/{collectionId}/attributes/datetime'.replace('{databaseId}',databaseId).replace('{collectionId}',collectionId);let payload={};if(typeof key!=='undefined'){payload['key']=key;}
|
||||
if(typeof required!=='undefined'){payload['required']=required;}
|
||||
if(typeof xdefault!=='undefined'){payload['default']=xdefault;}
|
||||
if(typeof array!=='undefined'){payload['array']=array;}
|
||||
const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('post',uri,{'content-type':'application/json',},payload);});}
|
||||
createEmailAttribute(databaseId,collectionId,key,required,xdefault,array){return __awaiter(this,void 0,void 0,function*(){if(typeof databaseId==='undefined'){throw new AppwriteException('Missing required parameter: "databaseId"');}
|
||||
if(typeof collectionId==='undefined'){throw new AppwriteException('Missing required parameter: "collectionId"');}
|
||||
if(typeof key==='undefined'){throw new AppwriteException('Missing required parameter: "key"');}
|
||||
|
|
@ -349,25 +355,23 @@ if(typeof cursorDirection!=='undefined'){payload['cursorDirection']=cursorDirect
|
|||
if(typeof orderAttributes!=='undefined'){payload['orderAttributes']=orderAttributes;}
|
||||
if(typeof orderTypes!=='undefined'){payload['orderTypes']=orderTypes;}
|
||||
const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('get',uri,{'content-type':'application/json',},payload);});}
|
||||
createDocument(databaseId,collectionId,documentId,data,read,write){return __awaiter(this,void 0,void 0,function*(){if(typeof databaseId==='undefined'){throw new AppwriteException('Missing required parameter: "databaseId"');}
|
||||
createDocument(databaseId,collectionId,documentId,data,permissions){return __awaiter(this,void 0,void 0,function*(){if(typeof databaseId==='undefined'){throw new AppwriteException('Missing required parameter: "databaseId"');}
|
||||
if(typeof collectionId==='undefined'){throw new AppwriteException('Missing required parameter: "collectionId"');}
|
||||
if(typeof documentId==='undefined'){throw new AppwriteException('Missing required parameter: "documentId"');}
|
||||
if(typeof data==='undefined'){throw new AppwriteException('Missing required parameter: "data"');}
|
||||
let path='/databases/{databaseId}/collections/{collectionId}/documents'.replace('{databaseId}',databaseId).replace('{collectionId}',collectionId);let payload={};if(typeof documentId!=='undefined'){payload['documentId']=documentId;}
|
||||
if(typeof data!=='undefined'){payload['data']=data;}
|
||||
if(typeof read!=='undefined'){payload['read']=read;}
|
||||
if(typeof write!=='undefined'){payload['write']=write;}
|
||||
if(typeof permissions!=='undefined'){payload['permissions']=permissions;}
|
||||
const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('post',uri,{'content-type':'application/json',},payload);});}
|
||||
getDocument(databaseId,collectionId,documentId){return __awaiter(this,void 0,void 0,function*(){if(typeof databaseId==='undefined'){throw new AppwriteException('Missing required parameter: "databaseId"');}
|
||||
if(typeof collectionId==='undefined'){throw new AppwriteException('Missing required parameter: "collectionId"');}
|
||||
if(typeof documentId==='undefined'){throw new AppwriteException('Missing required parameter: "documentId"');}
|
||||
let path='/databases/{databaseId}/collections/{collectionId}/documents/{documentId}'.replace('{databaseId}',databaseId).replace('{collectionId}',collectionId).replace('{documentId}',documentId);let payload={};const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('get',uri,{'content-type':'application/json',},payload);});}
|
||||
updateDocument(databaseId,collectionId,documentId,data,read,write){return __awaiter(this,void 0,void 0,function*(){if(typeof databaseId==='undefined'){throw new AppwriteException('Missing required parameter: "databaseId"');}
|
||||
updateDocument(databaseId,collectionId,documentId,data,permissions){return __awaiter(this,void 0,void 0,function*(){if(typeof databaseId==='undefined'){throw new AppwriteException('Missing required parameter: "databaseId"');}
|
||||
if(typeof collectionId==='undefined'){throw new AppwriteException('Missing required parameter: "collectionId"');}
|
||||
if(typeof documentId==='undefined'){throw new AppwriteException('Missing required parameter: "documentId"');}
|
||||
let path='/databases/{databaseId}/collections/{collectionId}/documents/{documentId}'.replace('{databaseId}',databaseId).replace('{collectionId}',collectionId).replace('{documentId}',documentId);let payload={};if(typeof data!=='undefined'){payload['data']=data;}
|
||||
if(typeof read!=='undefined'){payload['read']=read;}
|
||||
if(typeof write!=='undefined'){payload['write']=write;}
|
||||
if(typeof permissions!=='undefined'){payload['permissions']=permissions;}
|
||||
const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('patch',uri,{'content-type':'application/json',},payload);});}
|
||||
deleteDocument(databaseId,collectionId,documentId){return __awaiter(this,void 0,void 0,function*(){if(typeof databaseId==='undefined'){throw new AppwriteException('Missing required parameter: "databaseId"');}
|
||||
if(typeof collectionId==='undefined'){throw new AppwriteException('Missing required parameter: "collectionId"');}
|
||||
|
|
@ -698,14 +702,13 @@ if(typeof cursor!=='undefined'){payload['cursor']=cursor;}
|
|||
if(typeof cursorDirection!=='undefined'){payload['cursorDirection']=cursorDirection;}
|
||||
if(typeof orderType!=='undefined'){payload['orderType']=orderType;}
|
||||
const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('get',uri,{'content-type':'application/json',},payload);});}
|
||||
createBucket(bucketId,name,permission,read,write,enabled,maximumFileSize,allowedFileExtensions,encryption,antivirus){return __awaiter(this,void 0,void 0,function*(){if(typeof bucketId==='undefined'){throw new AppwriteException('Missing required parameter: "bucketId"');}
|
||||
createBucket(bucketId,name,fileSecurity,permissions,enabled,maximumFileSize,allowedFileExtensions,encryption,antivirus){return __awaiter(this,void 0,void 0,function*(){if(typeof bucketId==='undefined'){throw new AppwriteException('Missing required parameter: "bucketId"');}
|
||||
if(typeof name==='undefined'){throw new AppwriteException('Missing required parameter: "name"');}
|
||||
if(typeof permission==='undefined'){throw new AppwriteException('Missing required parameter: "permission"');}
|
||||
if(typeof fileSecurity==='undefined'){throw new AppwriteException('Missing required parameter: "fileSecurity"');}
|
||||
let path='/storage/buckets';let payload={};if(typeof bucketId!=='undefined'){payload['bucketId']=bucketId;}
|
||||
if(typeof name!=='undefined'){payload['name']=name;}
|
||||
if(typeof permission!=='undefined'){payload['permission']=permission;}
|
||||
if(typeof read!=='undefined'){payload['read']=read;}
|
||||
if(typeof write!=='undefined'){payload['write']=write;}
|
||||
if(typeof permissions!=='undefined'){payload['permissions']=permissions;}
|
||||
if(typeof fileSecurity!=='undefined'){payload['fileSecurity']=fileSecurity;}
|
||||
if(typeof enabled!=='undefined'){payload['enabled']=enabled;}
|
||||
if(typeof maximumFileSize!=='undefined'){payload['maximumFileSize']=maximumFileSize;}
|
||||
if(typeof allowedFileExtensions!=='undefined'){payload['allowedFileExtensions']=allowedFileExtensions;}
|
||||
|
|
@ -714,13 +717,12 @@ if(typeof antivirus!=='undefined'){payload['antivirus']=antivirus;}
|
|||
const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('post',uri,{'content-type':'application/json',},payload);});}
|
||||
getBucket(bucketId){return __awaiter(this,void 0,void 0,function*(){if(typeof bucketId==='undefined'){throw new AppwriteException('Missing required parameter: "bucketId"');}
|
||||
let path='/storage/buckets/{bucketId}'.replace('{bucketId}',bucketId);let payload={};const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('get',uri,{'content-type':'application/json',},payload);});}
|
||||
updateBucket(bucketId,name,permission,read,write,enabled,maximumFileSize,allowedFileExtensions,encryption,antivirus){return __awaiter(this,void 0,void 0,function*(){if(typeof bucketId==='undefined'){throw new AppwriteException('Missing required parameter: "bucketId"');}
|
||||
updateBucket(bucketId,name,fileSecurity,permissions,enabled,maximumFileSize,allowedFileExtensions,encryption,antivirus){return __awaiter(this,void 0,void 0,function*(){if(typeof bucketId==='undefined'){throw new AppwriteException('Missing required parameter: "bucketId"');}
|
||||
if(typeof name==='undefined'){throw new AppwriteException('Missing required parameter: "name"');}
|
||||
if(typeof permission==='undefined'){throw new AppwriteException('Missing required parameter: "permission"');}
|
||||
if(typeof fileSecurity==='undefined'){throw new AppwriteException('Missing required parameter: "fileSecurity"');}
|
||||
let path='/storage/buckets/{bucketId}'.replace('{bucketId}',bucketId);let payload={};if(typeof name!=='undefined'){payload['name']=name;}
|
||||
if(typeof permission!=='undefined'){payload['permission']=permission;}
|
||||
if(typeof read!=='undefined'){payload['read']=read;}
|
||||
if(typeof write!=='undefined'){payload['write']=write;}
|
||||
if(typeof permissions!=='undefined'){payload['permissions']=permissions;}
|
||||
if(typeof fileSecurity!=='undefined'){payload['fileSecurity']=fileSecurity;}
|
||||
if(typeof enabled!=='undefined'){payload['enabled']=enabled;}
|
||||
if(typeof maximumFileSize!=='undefined'){payload['maximumFileSize']=maximumFileSize;}
|
||||
if(typeof allowedFileExtensions!=='undefined'){payload['allowedFileExtensions']=allowedFileExtensions;}
|
||||
|
|
@ -737,13 +739,12 @@ if(typeof cursor!=='undefined'){payload['cursor']=cursor;}
|
|||
if(typeof cursorDirection!=='undefined'){payload['cursorDirection']=cursorDirection;}
|
||||
if(typeof orderType!=='undefined'){payload['orderType']=orderType;}
|
||||
const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('get',uri,{'content-type':'application/json',},payload);});}
|
||||
createFile(bucketId,fileId,file,read,write,onProgress=(progress)=>{}){return __awaiter(this,void 0,void 0,function*(){if(typeof bucketId==='undefined'){throw new AppwriteException('Missing required parameter: "bucketId"');}
|
||||
createFile(bucketId,fileId,file,permissions,onProgress=(progress)=>{}){return __awaiter(this,void 0,void 0,function*(){if(typeof bucketId==='undefined'){throw new AppwriteException('Missing required parameter: "bucketId"');}
|
||||
if(typeof fileId==='undefined'){throw new AppwriteException('Missing required parameter: "fileId"');}
|
||||
if(typeof file==='undefined'){throw new AppwriteException('Missing required parameter: "file"');}
|
||||
let path='/storage/buckets/{bucketId}/files'.replace('{bucketId}',bucketId);let payload={};if(typeof fileId!=='undefined'){payload['fileId']=fileId;}
|
||||
if(typeof file!=='undefined'){payload['file']=file;}
|
||||
if(typeof read!=='undefined'){payload['read']=read;}
|
||||
if(typeof write!=='undefined'){payload['write']=write;}
|
||||
if(typeof permissions!=='undefined'){payload['permissions']=permissions;}
|
||||
const uri=new URL(this.client.config.endpoint+path);if(!(file instanceof File)){throw new AppwriteException('Parameter "file" has to be a File.');}
|
||||
const size=file.size;if(size<=Service.CHUNK_SIZE){return yield this.client.call('post',uri,{'content-type':'multipart/form-data',},payload);}
|
||||
let id=undefined;let response=undefined;const headers={'content-type':'multipart/form-data',};let counter=0;const totalCounters=Math.ceil(size/Service.CHUNK_SIZE);if(fileId!='unique()'){try{response=yield this.client.call('GET',new URL(this.client.config.endpoint+path+'/'+fileId),headers);counter=response.chunksUploaded;}
|
||||
|
|
@ -755,10 +756,9 @@ return response;});}
|
|||
getFile(bucketId,fileId){return __awaiter(this,void 0,void 0,function*(){if(typeof bucketId==='undefined'){throw new AppwriteException('Missing required parameter: "bucketId"');}
|
||||
if(typeof fileId==='undefined'){throw new AppwriteException('Missing required parameter: "fileId"');}
|
||||
let path='/storage/buckets/{bucketId}/files/{fileId}'.replace('{bucketId}',bucketId).replace('{fileId}',fileId);let payload={};const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('get',uri,{'content-type':'application/json',},payload);});}
|
||||
updateFile(bucketId,fileId,read,write){return __awaiter(this,void 0,void 0,function*(){if(typeof bucketId==='undefined'){throw new AppwriteException('Missing required parameter: "bucketId"');}
|
||||
updateFile(bucketId,fileId,permissions){return __awaiter(this,void 0,void 0,function*(){if(typeof bucketId==='undefined'){throw new AppwriteException('Missing required parameter: "bucketId"');}
|
||||
if(typeof fileId==='undefined'){throw new AppwriteException('Missing required parameter: "fileId"');}
|
||||
let path='/storage/buckets/{bucketId}/files/{fileId}'.replace('{bucketId}',bucketId).replace('{fileId}',fileId);let payload={};if(typeof read!=='undefined'){payload['read']=read;}
|
||||
if(typeof write!=='undefined'){payload['write']=write;}
|
||||
let path='/storage/buckets/{bucketId}/files/{fileId}'.replace('{bucketId}',bucketId).replace('{fileId}',fileId);let payload={};if(typeof permissions!=='undefined'){payload['permissions']=permissions;}
|
||||
const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('put',uri,{'content-type':'application/json',},payload);});}
|
||||
deleteFile(bucketId,fileId){return __awaiter(this,void 0,void 0,function*(){if(typeof bucketId==='undefined'){throw new AppwriteException('Missing required parameter: "bucketId"');}
|
||||
if(typeof fileId==='undefined'){throw new AppwriteException('Missing required parameter: "fileId"');}
|
||||
|
|
@ -994,7 +994,17 @@ updatePhoneVerification(userId,phoneVerification){return __awaiter(this,void 0,v
|
|||
if(typeof phoneVerification==='undefined'){throw new AppwriteException('Missing required parameter: "phoneVerification"');}
|
||||
let path='/users/{userId}/verification/phone'.replace('{userId}',userId);let payload={};if(typeof phoneVerification!=='undefined'){payload['phoneVerification']=phoneVerification;}
|
||||
const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('patch',uri,{'content-type':'application/json',},payload);});}}
|
||||
exports.Account=Account;exports.AppwriteException=AppwriteException;exports.Avatars=Avatars;exports.Client=Client;exports.Databases=Databases;exports.Functions=Functions;exports.Health=Health;exports.Locale=Locale;exports.Projects=Projects;exports.Query=Query;exports.Storage=Storage;exports.Teams=Teams;exports.Users=Users;Object.defineProperty(exports,'__esModule',{value:true});})(this.Appwrite=this.Appwrite||{},null,window);(function(global,factory){typeof exports==='object'&&typeof module!=='undefined'?module.exports=factory():typeof define==='function'&&define.amd?define(factory):(global=typeof globalThis!=='undefined'?globalThis:global||self,global.Chart=factory());})(this,(function(){'use strict';function noop(){}
|
||||
class Permission{}
|
||||
Permission.read=(role)=>{return`read("${role}")`;};Permission.write=(role)=>{return`write("${role}")`;};Permission.create=(role)=>{return`create("${role}")`;};Permission.update=(role)=>{return`update("${role}")`;};Permission.delete=(role)=>{return`delete("${role}")`;};class Role{static any(){return'any';}
|
||||
static user(id){return`user:${id}`;}
|
||||
static users(){return'users';}
|
||||
static guests(){return'guests';}
|
||||
static team(id,role=''){if(role===''){return`team:${id}`;}
|
||||
return`team:${id}/${role}`;}
|
||||
static status(status){return`status:${status}`;}}
|
||||
class ID{static custom(id){return id;}
|
||||
static unique(){return'unique()';}}
|
||||
exports.Account=Account;exports.AppwriteException=AppwriteException;exports.Avatars=Avatars;exports.Client=Client;exports.Databases=Databases;exports.Functions=Functions;exports.Health=Health;exports.ID=ID;exports.Locale=Locale;exports.Permission=Permission;exports.Projects=Projects;exports.Query=Query;exports.Role=Role;exports.Storage=Storage;exports.Teams=Teams;exports.Users=Users;Object.defineProperty(exports,'__esModule',{value:true});})(this.Appwrite=this.Appwrite||{},null,window);(function(global,factory){typeof exports==='object'&&typeof module!=='undefined'?module.exports=factory():typeof define==='function'&&define.amd?define(factory):(global=typeof globalThis!=='undefined'?globalThis:global||self,global.Chart=factory());})(this,(function(){'use strict';function noop(){}
|
||||
const uid=(function(){let id=0;return function(){return id++;};}());function isNullOrUndef(value){return value===null||typeof value==='undefined';}
|
||||
function isArray(value){if(Array.isArray&&Array.isArray(value)){return true;}
|
||||
const type=Object.prototype.toString.call(value);if(type.slice(0,7)==='[object'&&type.slice(-6)==='Array]'){return true;}
|
||||
|
|
|
|||
141
public/dist/scripts/app.js
vendored
141
public/dist/scripts/app.js
vendored
|
|
@ -521,115 +521,15 @@ params=formData;break;}
|
|||
return new Promise(function(resolve,reject){let request=new XMLHttpRequest(),key;request.withCredentials=true;request.open(method,path,true);for(key in headers){if(headers.hasOwnProperty(key)){request.setRequestHeader(key,headers[key]);}}
|
||||
request.onload=function(){if(4===request.readyState&&399>=request.status){let data=request.response;let contentType=this.getResponseHeader('content-type');contentType=contentType.substring(0,contentType.indexOf(';'));switch(contentType){case'application/json':data=JSON.parse(data);break;}
|
||||
resolve(data);}else{reject(new Error(request.statusText));}};if(progress){request.addEventListener('progress',progress);request.upload.addEventListener('progress',progress,false);}
|
||||
request.onerror=function(){reject(new Error("Network Error"));};request.send(params);})};return{'get':function(path,headers={},params={}){return call('GET',path+((params.length>0)?'?'+buildQuery(params):''),headers,{});},'post':function(path,headers={},params={},progress=null){return call('POST',path,headers,params,progress);},'put':function(path,headers={},params={},progress=null){return call('PUT',headers,params,progress);},'patch':function(path,headers={},params={},progress=null){return call('PATCH',path,headers,params,progress);},'delete':function(path,headers={},params={},progress=null){return call('DELETE',path,headers,params,progress);},'addGlobalParam':addGlobalParam,'addGlobalHeader':addGlobalHeader}}(window.document);let analytics={create:function(id,source,activity,url){return http.post('/analytics',{'content-type':'application/json'},{id:id,source:source,activity:activity,url:url,version:env.VERSION,setup:env.SETUP});},};return{analytics:analytics,};},true);})(window);(function(window){"use strict";window.ls.container.set('console',function(window){var client=new Appwrite.Client();var endpoint=window.location.origin+'/v1';client.setEndpoint(endpoint).setProject('console').setLocale(APP_ENV.LOCALE);return{client:client,account:new Appwrite.Account(client),avatars:new Appwrite.Avatars(client),databases:new Appwrite.Databases(client),functions:new Appwrite.Functions(client),health:new Appwrite.Health(client),locale:new Appwrite.Locale(client),projects:new Appwrite.Projects(client),storage:new Appwrite.Storage(client),teams:new Appwrite.Teams(client),users:new Appwrite.Users(client)}},true);})(window);(function(window){"use strict";window.ls.container.set('date',function(){function format(format,timestamp){var jsdate,f
|
||||
var txtWords=['Sun','Mon','Tues','Wednes','Thurs','Fri','Satur','January','February','March','April','May','June','July','August','September','October','November','December']
|
||||
var formatChr=/\\?(.?)/gi
|
||||
var formatChrCb=function(t,s){return f[t]?f[t]():s}
|
||||
var _pad=function(n,c){n=String(n)
|
||||
while(n.length<c){n='0'+n}
|
||||
return n}
|
||||
f={d:function(){return _pad(f.j(),2)},D:function(){return f.l().slice(0,3)},j:function(){return jsdate.getDate()},l:function(){return txtWords[f.w()]+'day'},N:function(){return f.w()||7},S:function(){var j=f.j()
|
||||
var i=j%10
|
||||
if(i<=3&&parseInt((j%100)/10,10)===1){i=0}
|
||||
return['st','nd','rd'][i-1]||'th'},w:function(){return jsdate.getDay()},z:function(){var a=new Date(f.Y(),f.n()-1,f.j())
|
||||
var b=new Date(f.Y(),0,1)
|
||||
return Math.round((a-b)/864e5)},W:function(){var a=new Date(f.Y(),f.n()-1,f.j()-f.N()+3)
|
||||
var b=new Date(a.getFullYear(),0,4)
|
||||
return _pad(1+Math.round((a-b)/864e5/7),2)},F:function(){return txtWords[6+f.n()]},m:function(){return _pad(f.n(),2)},M:function(){return f.F().slice(0,3)},n:function(){return jsdate.getMonth()+1},t:function(){return(new Date(f.Y(),f.n(),0)).getDate()},L:function(){var j=f.Y()
|
||||
return j%4===0&j%100!==0|j%400===0},o:function(){var n=f.n()
|
||||
var W=f.W()
|
||||
var Y=f.Y()
|
||||
return Y+(n===12&&W<9?1:n===1&&W>9?-1:0)},Y:function(){return jsdate.getFullYear()},y:function(){return f.Y().toString().slice(-2)},a:function(){return jsdate.getHours()>11?'pm':'am'},A:function(){return f.a().toUpperCase()},B:function(){var H=jsdate.getUTCHours()*36e2
|
||||
var i=jsdate.getUTCMinutes()*60
|
||||
var s=jsdate.getUTCSeconds()
|
||||
return _pad(Math.floor((H+i+s+36e2)/86.4)%1e3,3)},g:function(){return f.G()%12||12},G:function(){return jsdate.getHours()},h:function(){return _pad(f.g(),2)},H:function(){return _pad(f.G(),2)},i:function(){return _pad(jsdate.getMinutes(),2)},s:function(){return _pad(jsdate.getSeconds(),2)},u:function(){return _pad(jsdate.getMilliseconds()*1000,6)},e:function(){var msg='Not supported (see source code of date() for timezone on how to add support)'
|
||||
throw new Error(msg)},I:function(){var a=new Date(f.Y(),0)
|
||||
var c=Date.UTC(f.Y(),0)
|
||||
var b=new Date(f.Y(),6)
|
||||
var d=Date.UTC(f.Y(),6)
|
||||
return((a-c)!==(b-d))?1:0},O:function(){var tzo=jsdate.getTimezoneOffset()
|
||||
var a=Math.abs(tzo)
|
||||
return(tzo>0?'-':'+')+_pad(Math.floor(a/60)*100+a%60,4)},P:function(){var O=f.O()
|
||||
return(O.substr(0,3)+':'+O.substr(3,2))},T:function(){return'UTC'},Z:function(){return-jsdate.getTimezoneOffset()*60},c:function(){return'Y-m-d\\TH:i:sP'.replace(formatChr,formatChrCb)},r:function(){return'D, d M Y H:i:s O'.replace(formatChr,formatChrCb)},U:function(){return jsdate/1000|0}}
|
||||
var _date=function(format,timestamp){jsdate=(timestamp===undefined?new Date():(timestamp instanceof Date)?new Date(timestamp):new Date(timestamp*1000))
|
||||
return format.replace(formatChr,formatChrCb)}
|
||||
return _date(format,timestamp)}
|
||||
function strtotime(text,now){var parsed
|
||||
var match
|
||||
var today
|
||||
var year
|
||||
var date
|
||||
var days
|
||||
var ranges
|
||||
var len
|
||||
var times
|
||||
var regex
|
||||
var i
|
||||
var fail=false
|
||||
if(!text){return fail}
|
||||
text=text.replace(/^\s+|\s+$/g,'').replace(/\s{2,}/g,' ').replace(/[\t\r\n]/g,'').toLowerCase()
|
||||
var pattern=new RegExp(['^(\\d{1,4})','([\\-\\.\\/:])','(\\d{1,2})','([\\-\\.\\/:])','(\\d{1,4})','(?:\\s(\\d{1,2}):(\\d{2})?:?(\\d{2})?)?','(?:\\s([A-Z]+)?)?$'].join(''))
|
||||
match=text.match(pattern)
|
||||
if(match&&match[2]===match[4]){if(match[1]>1901){switch(match[2]){case'-':if(match[3]>12||match[5]>31){return fail}
|
||||
return new Date(match[1],parseInt(match[3],10)-1,match[5],match[6]||0,match[7]||0,match[8]||0,match[9]||0)/1000
|
||||
case'.':return fail
|
||||
case'/':if(match[3]>12||match[5]>31){return fail}
|
||||
return new Date(match[1],parseInt(match[3],10)-1,match[5],match[6]||0,match[7]||0,match[8]||0,match[9]||0)/1000}}else if(match[5]>1901){switch(match[2]){case'-':if(match[3]>12||match[1]>31){return fail}
|
||||
return new Date(match[5],parseInt(match[3],10)-1,match[1],match[6]||0,match[7]||0,match[8]||0,match[9]||0)/1000
|
||||
case'.':if(match[3]>12||match[1]>31){return fail}
|
||||
return new Date(match[5],parseInt(match[3],10)-1,match[1],match[6]||0,match[7]||0,match[8]||0,match[9]||0)/1000
|
||||
case'/':if(match[1]>12||match[3]>31){return fail}
|
||||
return new Date(match[5],parseInt(match[1],10)-1,match[3],match[6]||0,match[7]||0,match[8]||0,match[9]||0)/1000}}else{switch(match[2]){case'-':if(match[3]>12||match[5]>31||(match[1]<70&&match[1]>38)){return fail}
|
||||
year=match[1]>=0&&match[1]<=38?+match[1]+2000:match[1]
|
||||
return new Date(year,parseInt(match[3],10)-1,match[5],match[6]||0,match[7]||0,match[8]||0,match[9]||0)/1000
|
||||
case'.':if(match[5]>=70){if(match[3]>12||match[1]>31){return fail}
|
||||
return new Date(match[5],parseInt(match[3],10)-1,match[1],match[6]||0,match[7]||0,match[8]||0,match[9]||0)/1000}
|
||||
if(match[5]<60&&!match[6]){if(match[1]>23||match[3]>59){return fail}
|
||||
today=new Date()
|
||||
return new Date(today.getFullYear(),today.getMonth(),today.getDate(),match[1]||0,match[3]||0,match[5]||0,match[9]||0)/1000}
|
||||
return fail
|
||||
case'/':if(match[1]>12||match[3]>31||(match[5]<70&&match[5]>38)){return fail}
|
||||
year=match[5]>=0&&match[5]<=38?+match[5]+2000:match[5]
|
||||
return new Date(year,parseInt(match[1],10)-1,match[3],match[6]||0,match[7]||0,match[8]||0,match[9]||0)/1000
|
||||
case':':if(match[1]>23||match[3]>59||match[5]>59){return fail}
|
||||
today=new Date()
|
||||
return new Date(today.getFullYear(),today.getMonth(),today.getDate(),match[1]||0,match[3]||0,match[5]||0)/1000}}}
|
||||
if(text==='now'){return now===null||isNaN(now)?new Date().getTime()/1000|0:now|0}
|
||||
if(!isNaN(parsed=Date.parse(text))){return parsed/1000|0}
|
||||
pattern=new RegExp(['^([0-9]{4}-[0-9]{2}-[0-9]{2})','[ t]','([0-9]{2}:[0-9]{2}:[0-9]{2}(\\.[0-9]+)?)','([\\+-][0-9]{2}(:[0-9]{2})?|z)'].join(''))
|
||||
match=text.match(pattern)
|
||||
if(match){if(match[4]==='z'){match[4]='Z'}else if(match[4].match(/^([+-][0-9]{2})$/)){match[4]=match[4]+':00'}
|
||||
if(!isNaN(parsed=Date.parse(match[1]+'T'+match[2]+match[4]))){return parsed/1000|0}}
|
||||
date=now?new Date(now*1000):new Date()
|
||||
days={'sun':0,'mon':1,'tue':2,'wed':3,'thu':4,'fri':5,'sat':6}
|
||||
ranges={'yea':'FullYear','mon':'Month','day':'Date','hou':'Hours','min':'Minutes','sec':'Seconds'}
|
||||
function lastNext(type,range,modifier){var diff
|
||||
var day=days[range]
|
||||
if(typeof day!=='undefined'){diff=day-date.getDay()
|
||||
if(diff===0){diff=7*modifier}else if(diff>0&&type==='last'){diff-=7}else if(diff<0&&type==='next'){diff+=7}
|
||||
date.setDate(date.getDate()+diff)}}
|
||||
function process(val){var splt=val.split(' ')
|
||||
var type=splt[0]
|
||||
var range=splt[1].substring(0,3)
|
||||
var typeIsNumber=/\d+/.test(type)
|
||||
var ago=splt[2]==='ago'
|
||||
var num=(type==='last'?-1:1)*(ago?-1:1)
|
||||
if(typeIsNumber){num*=parseInt(type,10)}
|
||||
if(ranges.hasOwnProperty(range)&&!splt[1].match(/^mon(day|\.)?$/i)){return date['set'+ranges[range]](date['get'+ranges[range]]()+num)}
|
||||
if(range==='wee'){return date.setDate(date.getDate()+(num*7))}
|
||||
if(type==='next'||type==='last'){lastNext(type,range,num)}else if(!typeIsNumber){return false}
|
||||
return true}
|
||||
times='(years?|months?|weeks?|days?|hours?|minutes?|min|seconds?|sec'+'|sunday|sun\\.?|monday|mon\\.?|tuesday|tue\\.?|wednesday|wed\\.?'+'|thursday|thu\\.?|friday|fri\\.?|saturday|sat\\.?)'
|
||||
regex='([+-]?\\d+\\s'+times+'|'+'(last|next)\\s'+times+')(\\sago)?'
|
||||
match=text.match(new RegExp(regex,'gi'))
|
||||
if(!match){return fail}
|
||||
for(i=0,len=match.length;i<len;i++){if(!process(match[i])){return fail}}
|
||||
return(date.getTime()/1000)}
|
||||
return{format:format,strtotime:strtotime}}(),true);})(window);(function(window){"use strict";window.ls.container.set('env',function(){return APP_ENV;},true);})(window);(function(window){"use strict";window.ls.container.set('form',function(){function cast(value,to){if(value&&Array.isArray(value)&&to!=='array'){value=value.map(element=>cast(element,to));return value;}
|
||||
request.onerror=function(){reject(new Error("Network Error"));};request.send(params);})};return{'get':function(path,headers={},params={}){return call('GET',path+((params.length>0)?'?'+buildQuery(params):''),headers,{});},'post':function(path,headers={},params={},progress=null){return call('POST',path,headers,params,progress);},'put':function(path,headers={},params={},progress=null){return call('PUT',headers,params,progress);},'patch':function(path,headers={},params={},progress=null){return call('PATCH',path,headers,params,progress);},'delete':function(path,headers={},params={},progress=null){return call('DELETE',path,headers,params,progress);},'addGlobalParam':addGlobalParam,'addGlobalHeader':addGlobalHeader}}(window.document);let analytics={create:function(id,source,activity,url){return http.post('/analytics',{'content-type':'application/json'},{id:id,source:source,activity:activity,url:url,version:env.VERSION,setup:env.SETUP});},};return{analytics:analytics,};},true);})(window);(function(window){"use strict";window.ls.container.set('console',function(window){var client=new Appwrite.Client();var endpoint=window.location.origin+'/v1';client.setEndpoint(endpoint).setProject('console').setLocale(APP_ENV.LOCALE);return{client:client,account:new Appwrite.Account(client),avatars:new Appwrite.Avatars(client),databases:new Appwrite.Databases(client),functions:new Appwrite.Functions(client),health:new Appwrite.Health(client),locale:new Appwrite.Locale(client),projects:new Appwrite.Projects(client),storage:new Appwrite.Storage(client),teams:new Appwrite.Teams(client),users:new Appwrite.Users(client)}},true);})(window);(function(window){"use strict";window.ls.container.set('date',function(){function format(format,datetime){if(!datetime){return null;}
|
||||
return new Intl.DateTimeFormat('en-US',{timeZone:'UTC',hourCycle:'h24',...format}).format(new Date(datetime));}
|
||||
return{format:format,}}(),true);})(window);(function(window){"use strict";window.ls.container.set('env',function(){return APP_ENV;},true);})(window);(function(window){"use strict";window.ls.container.set('form',function(){function cast(value,from,to,){if(value&&Array.isArray(value)&&to!=='array'){value=value.map(element=>cast(element,from,to));return value;}
|
||||
switch(to){case'int':case'integer':value=parseInt(value);break;case'numeric':value=Number(value);break;case'float':value=parseFloat(value);break;case'string':value=value.toString();if(value.length===0){value=null;}
|
||||
break;case'json':value=(value)?JSON.parse(value):[];break;case'array':value=(value&&value.constructor&&value.constructor===Array)?value:[value];break;case'array-empty':value=[];break;case'bool':case'boolean':value=(value==='false')?false:value;value=!!value;break;}
|
||||
break;case'json':value=(value)?JSON.parse(value):[];break;case'array':if(value&&value.constructor&&value.constructor===Array){break;}
|
||||
if(from==='csv'){if(value.length===0){value=[];}else{value=value.split(',');}}else{value=[value];}
|
||||
break;case'array-empty':value=[];break;case'bool':case'boolean':value=(value==='false')?false:value;value=!!value;break;}
|
||||
return value;}
|
||||
function toJson(element,json){json=json||{};let name=element.getAttribute('name');let type=element.getAttribute('type');let castTo=element.getAttribute('data-cast-to');let ref=json;if(name&&'FORM'!==element.tagName){if(name.startsWith('[')){let splitName=name.split('.');if(splitName.length>1&&splitName[0].endsWith(']')){name=splitName[splitName.length-1];}}
|
||||
function toJson(element,json){json=json||{};let name=element.getAttribute('name');let type=element.getAttribute('type');let castTo=element.getAttribute('data-cast-to');let castFrom=element.getAttribute('data-cast-from');let ref=json;if(name&&'FORM'!==element.tagName){if(name.startsWith('[')){let splitName=name.split('.');if(splitName.length>1&&splitName[0].endsWith(']')){name=splitName[splitName.length-1];}}
|
||||
if('FIELDSET'===element.tagName){if(castTo==='object'){if(json[name]===undefined){json[name]={};}
|
||||
ref=json[name];}
|
||||
else{if(!Array.isArray(json[name])){json[name]=[];}
|
||||
|
|
@ -642,7 +542,7 @@ else if('file'===type){json[name]=element.files[0];}
|
|||
else if(undefined!==element.value){if((json[name]!==undefined)&&(!Array.isArray(json[name]))){json[name]=[json[name]];}
|
||||
if(Array.isArray(json[name])){json[name].push(element.value);}
|
||||
else{json[name]=element.value;}}
|
||||
json[name]=cast(json[name],castTo);}}
|
||||
json[name]=cast(json[name],castFrom,castTo);}}
|
||||
for(let i=0;i<element.children.length;i++){if(Array.isArray(ref)){ref.push({});toJson(element.children[i],ref[ref.length]);}
|
||||
else{toJson(element.children[i],ref);}}
|
||||
return json;}
|
||||
|
|
@ -655,7 +555,7 @@ return false;};return{isRTL:isRTL,};},true);})(window);(function(window){"use st
|
|||
let size=element.dataset["size"]||80;let name=$value.name||$value||"";name=(typeof name!=='string')?'--':name;return def="/v1/avatars/initials?project=console"+"&name="+
|
||||
encodeURIComponent(name)+"&width="+
|
||||
size+"&height="+
|
||||
size;}).add("selectedCollection",function($value,router){return $value===router.params.collectionId?"selected":"";}).add("selectedDocument",function($value,router){return $value===router.params.documentId?"selected":"";}).add("localeString",function($value){$value=parseInt($value);return!Number.isNaN($value)?$value.toLocaleString():"";}).add("date",function($value,date){return $value?date.format("Y-m-d",$value):"";}).add("dateTime",function($value,date){return $value?date.format("Y-m-d H:i",$value):"";}).add("dateText",function($value,date){return $value?date.format("d M Y",$value):"";}).add("timeSince",function($value){$value=$value*1000;let seconds=Math.floor((Date.now()-$value)/1000);let unit="second";let direction="ago";if(seconds<0){seconds=-seconds;direction="from now";}
|
||||
size;}).add("selectedCollection",function($value,router){return $value===router.params.collectionId?"selected":"";}).add("selectedDocument",function($value,router){return $value===router.params.documentId?"selected":"";}).add("localeString",function($value){$value=parseInt($value);return!Number.isNaN($value)?$value.toLocaleString():"";}).add("dateTime",function($value,date){return $value?date.format({year:'numeric',month:'2-digit',day:'2-digit',hour:'2-digit',minute:'2-digit'},$value):"";}).add("date",function($value,date){return $value?date.format({year:'numeric',month:'short',day:'2-digit',},$value):"";}).add("timeSince",function($value){$value=new Date($value).getTime();let now=new Date();now.setMinutes(now.getMinutes()+now.getTimezoneOffset());let timestamp=new Date(now.toISOString()).getTime();let seconds=Math.floor((timestamp-$value)/1000);let unit="second";let direction="ago";if(seconds<0){seconds=-seconds;direction="from now";}
|
||||
let value=seconds;if(seconds>=31536000){value=Math.floor(seconds/31536000);unit="year";}
|
||||
else if(seconds>=86400){value=Math.floor(seconds/86400);unit="day";}
|
||||
else if(seconds>=3600){value=Math.floor(seconds/3600);unit="hour";}
|
||||
|
|
@ -693,11 +593,10 @@ if(forcePlaces!==false){rounded=Number(rounded).toFixed(forcePlaces);}
|
|||
return rounded+abbr;}
|
||||
window.ls.container.get("view").add({selector:"data-acl",controller:function(element,document,router,alerts){document.body.classList.remove("console");document.body.classList.remove("home");document.body.classList.add(router.getCurrent().view.scope);if(!router.getCurrent().view.project){document.body.classList.add("hide-nav");document.body.classList.remove("show-nav");}else{document.body.classList.add("show-nav");document.body.classList.remove("hide-nav");}
|
||||
if("/console"===router.getCurrent().path){document.body.classList.add("index");}else{document.body.classList.remove("index");}}}).add({selector:"data-prism",controller:function(window,document,element,alerts){Prism.highlightElement(element);let copy=document.createElement("i");copy.className="icon-docs copy";copy.title="Copy to Clipboard";copy.textContent="Click Here to Copy";copy.addEventListener("click",function(){window.getSelection().removeAllRanges();let range=document.createRange();range.selectNode(element);window.getSelection().addRange(range);try{document.execCommand("copy");alerts.add({text:"Copied to clipboard",class:""},3000);}catch(err){alerts.add({text:"Failed to copy text ",class:"error"},3000);}
|
||||
window.getSelection().removeAllRanges();});element.parentNode.parentNode.appendChild(copy);}});(function(window){document.addEventListener('alpine:init',()=>{Alpine.store('uploader',{_files:[],files(){return(this._files??[]).filter((file)=>!file.cancelled);},isOpen:true,init(){window.addEventListener('beforeunload',(event)=>{if(this.hasOngoingUploads()){let confirmationMessage="There are incomplete uploads, are you sure you want to leave?";event.returnValue=confirmationMessage;return confirmationMessage;}});},cancelAll(){if(this.hasOngoingUploads()?confirm("Are you sure? This will cancel and remove any ongoing uploads?"):true){this._files.forEach(file=>{if(file.completed||file.failed){this.removeFile(file.id);}else{this.updateFile(file.id,{cancelled:true});}});}},hasOngoingUploads(){let ongoing=false;this._files.some((file)=>{if(!file.completed&&!file.failed){ongoing=true;return;}});return ongoing;},toggle(){this.isOpen=!this.isOpen;},addFile(file){this._files.push(file);},updateFile(id,file){this._files=this._files.map((oldFile)=>id==oldFile.id?{...oldFile,...file}:oldFile);},removeFile(id){const file=this.getFile(id)??{};if(file.completed||file.failed){this._files=this._files.filter((file)=>file.id!==id);}else{if(confirm("Are you sure you want to cancel the upload?")){this.updateFile(id,{cancelled:true});}}},getFile(id){return this._files.find((file)=>file.id===id);},async uploadFile(target){const formData=new FormData(target);const sdk=window.ls.container.get('sdk');const bucketId=formData.get('bucketId');const file=formData.get('file');const fileId=formData.get('fileId');let id=fileId==='unique()'?performance.now():fileId;let read=formData.get('read');if(!file||!fileId){return;}
|
||||
if(read){read=JSON.parse(read);}
|
||||
let write=formData.get('write');if(write){write=JSON.parse(write);}
|
||||
window.getSelection().removeAllRanges();});element.parentNode.parentNode.appendChild(copy);}});(function(window){document.addEventListener('alpine:init',()=>{Alpine.store('uploader',{_files:[],files(){return(this._files??[]).filter((file)=>!file.cancelled);},isOpen:true,init(){window.addEventListener('beforeunload',(event)=>{if(this.hasOngoingUploads()){let confirmationMessage="There are incomplete uploads, are you sure you want to leave?";event.returnValue=confirmationMessage;return confirmationMessage;}});},cancelAll(){if(this.hasOngoingUploads()?confirm("Are you sure? This will cancel and remove any ongoing uploads?"):true){this._files.forEach(file=>{if(file.completed||file.failed){this.removeFile(file.id);}else{this.updateFile(file.id,{cancelled:true});}});}},hasOngoingUploads(){let ongoing=false;this._files.some((file)=>{if(!file.completed&&!file.failed){ongoing=true;return;}});return ongoing;},toggle(){this.isOpen=!this.isOpen;},addFile(file){this._files.push(file);},updateFile(id,file){this._files=this._files.map((oldFile)=>id==oldFile.id?{...oldFile,...file}:oldFile);},removeFile(id){const file=this.getFile(id)??{};if(file.completed||file.failed){this._files=this._files.filter((file)=>file.id!==id);}else{if(confirm("Are you sure you want to cancel the upload?")){this.updateFile(id,{cancelled:true});}}},getFile(id){return this._files.find((file)=>file.id===id);},async uploadFile(target){const formData=new FormData(target);const sdk=window.ls.container.get('sdk');const bucketId=formData.get('bucketId');const file=formData.get('file');const fileId=formData.get('fileId');let id=fileId==='unique()'?performance.now():fileId;if(!file||!fileId){return;}
|
||||
let permissions=formData.get('permissions');if(permissions){permissions=permissions.split(',');}
|
||||
if(this.getFile(id)){this.updateFile(id,{name:file.name,completed:false,failed:false,cancelled:false,error:"",});}else{this.addFile({id:id,name:file.name,progress:0,completed:false,failed:false,cancelled:false,error:"",});}
|
||||
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;}
|
||||
target.reset();try{const response=await sdk.storage.createFile(bucketId,fileId,file,permissions,(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){document.addEventListener('alpine:init',()=>{Alpine.data('events',()=>({events:new Set(),selected:null,action:null,type:null,subType:null,subSubType:null,resource:null,resourceName:'',subResource:null,subResourceName:'',subSubResource:null,subSubResourceName:'',hasResource:false,hasSubResource:false,hasSubSubResource:false,attribute:null,hasAttribute:false,attributes:[],load(events){this.events=new Set(events);},reset(){this.hasResource=this.hasSubResource=this.hasSubSubResource=this.hasAttribute=false;this.type=this.subType=this.subResource=this.resource=this.attribute=this.selected=this.action=null;},setEvent(){this.hasResource=this.hasSubResource=this.hasSubSubResource=this.hasAttribute=this.action=false;if(!this.selected){this.reset();return;}
|
||||
let[type,action]=this.selected.split('.');switch(type){case'users':if(action==='update'){this.hasAttribute=true;this.attributes=['email','name','password','status','prefs']}
|
||||
|
|
@ -707,7 +606,14 @@ this.action=action;},showModal(modal){document.documentElement.classList.add("mo
|
|||
if(this.hasSubSubResource){event+=`.${this.subSubType}.${this.subSubResource ? this.subSubResource : '*'}`;}
|
||||
if(this.action){event+=`.${this.action}`;}
|
||||
if(this.attribute){event+=`.${this.attribute}`;}
|
||||
this.events.add(event);this.reset();},removeEvent(value){this.events.delete(value);}}));});})(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();}
|
||||
this.events.add(event);this.reset();},removeEvent(value){this.events.delete(value);}}));});})(window);(function(window){document.addEventListener('alpine:init',()=>{Alpine.data('permissionsMatrix',()=>({permissions:[],rawPermissions:[],load(permissions){if(permissions===undefined){return;}
|
||||
this.rawPermissions=permissions;permissions.map(p=>{let{type,role}=this.parsePermission(p);type=this.parseInputPermission(type);let index=-1;let existing=this.permissions.find((p,idx)=>{if(p.role===role){index=idx;return true;}})
|
||||
if(existing===undefined){this.permissions.push({role,[type]:true,});}
|
||||
if(index!==-1){existing[type]=true;this.permissions[index]=existing;}});},addPermission(formId,role,permissions){if(!document.getElementById(formId).reportValidity()){return;}
|
||||
Object.entries(permissions).forEach(entry=>{let[type,enabled]=entry;type=this.parseOutputPermission(type);if(enabled){this.rawPermissions.push(this.buildPermission(type,role));}});this.permissions.push({role,...permissions,});this.reset();},updatePermission(index){setTimeout(()=>{const permission=this.permissions[index];Object.keys(permission).forEach(key=>{if(key==='role'){return;}
|
||||
const parsedKey=this.parseOutputPermission(key);const permissionString=this.buildPermission(parsedKey,permission.role);if(permission[key]){if(!this.rawPermissions.includes(permissionString)){this.rawPermissions.push(permissionString);}}else{this.rawPermissions=this.rawPermissions.filter(p=>{return!p.includes(permissionString);});}});});},removePermission(index){let row=this.permissions.splice(index,1);if(row.length===1){this.rawPermissions=this.rawPermissions.filter(p=>!p.includes(row[0].role));}},parsePermission(permission){let parts=permission.split('(');let type=parts[0];let role=parts[1].replace(')','').replace(' ','').replaceAll('"','');return{type,role};},buildPermission(type,role){return`${type}("${role}")`},parseInputPermission(key){if(key==='delete'){return'xdelete';}
|
||||
return key;},parseOutputPermission(key){if(key==='xdelete'){return'delete';}
|
||||
return key;}}));Alpine.data('permissionsRow',()=>({role:'',read:false,create:false,update:false,xdelete:false,reset(){this.role='';this.read=this.create=this.update=this.xdelete=false;}}));});})(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"},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];}
|
||||
|
|
@ -753,9 +659,9 @@ button.addEventListener("click",function(){var clone=document.createElement(elem
|
|||
clone.innerHTML=template;clone.className=element.className;var input=clone.querySelector("input, select, textarea");view.render(clone);if(debug){console.log('Debug: clone: ',clone);console.log('Debug: target: ',target);}
|
||||
if(target){target.appendChild(clone);}else{button.parentNode.insertBefore(clone,button);}
|
||||
if(input){input.focus();}
|
||||
Array.prototype.slice.call(clone.querySelectorAll("[data-remove]")).map(function(obj){obj.addEventListener("click",function(){clone.parentNode.removeChild(clone);obj.scrollIntoView({behavior:"smooth"});});});Array.prototype.slice.call(clone.querySelectorAll("[data-up]")).map(function(obj){obj.addEventListener("click",function(){if(clone.previousElementSibling){clone.parentNode.insertBefore(clone,clone.previousElementSibling);obj.scrollIntoView({behavior:"smooth"});}});});Array.prototype.slice.call(clone.querySelectorAll("[data-down]")).map(function(obj){obj.addEventListener("click",function(){if(clone.nextElementSibling){clone.parentNode.insertBefore(clone.nextElementSibling,clone);obj.scrollIntoView({behavior:"smooth"});}});});});element.parentNode.insertBefore(button,element.nextSibling);element.parentNode.removeChild(element);button.form.addEventListener('reset',function(event){target.innerHTML='';if(first){button.click();}});if(first){button.click();}}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-add",repeat:false,controller:function(element,view,container,document){for(var i=0;i<element.children.length;i++){let button=document.createElement("button");let template=element.children[i].cloneNode(true);let as=element.getAttribute('data-ls-as');let counter=0;button.type="button";button.innerText="Add";button.classList.add("reverse");button.classList.add("margin-end-small");button.addEventListener('click',function(){container.addNamespace(as,'new-'+counter++);console.log(container.namespaces,container.get(as),as);container.set(as,null,true,true);let child=template.cloneNode(true);view.render(child);element.appendChild(child);element.style.visibility='visible';let inputs=child.querySelectorAll('input,textarea');for(let index=0;index<inputs.length;++index){if(inputs[index].type!=='hidden'){inputs[index].focus();break;}}});element.after(button);}}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-chart",controller:function(element,container,date,document){let wrapper=document.createElement("div");let child=document.createElement("canvas");let sources=element.getAttribute('data-forms-chart');let width=element.getAttribute('data-width')||500;let height=element.getAttribute('data-height')||175;let showXAxis=element.getAttribute('data-show-x-axis')||false;let showYAxis=element.getAttribute('data-show-y-axis')||false;let colors=(element.getAttribute('data-colors')||'blue,green,orange,red').split(',');let themes={'blue':'#29b5d9','green':'#4eb55b','orange':'#fba233','red':'#dc3232','create':'#00b680','read':'#009cde','update':'#696fd7','delete':'#da5d95',};let range={'24h':'H:i','7d':'d F Y','30d':'d F Y','90d':'d F Y'}
|
||||
Array.prototype.slice.call(clone.querySelectorAll("[data-remove]")).map(function(obj){obj.addEventListener("click",function(){clone.parentNode.removeChild(clone);obj.scrollIntoView({behavior:"smooth"});});});Array.prototype.slice.call(clone.querySelectorAll("[data-up]")).map(function(obj){obj.addEventListener("click",function(){if(clone.previousElementSibling){clone.parentNode.insertBefore(clone,clone.previousElementSibling);obj.scrollIntoView({behavior:"smooth"});}});});Array.prototype.slice.call(clone.querySelectorAll("[data-down]")).map(function(obj){obj.addEventListener("click",function(){if(clone.nextElementSibling){clone.parentNode.insertBefore(clone.nextElementSibling,clone);obj.scrollIntoView({behavior:"smooth"});}});});});element.parentNode.insertBefore(button,element.nextSibling);element.parentNode.removeChild(element);button.form.addEventListener('reset',function(event){target.innerHTML='';if(first){button.click();}});if(first){button.click();}}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-add",repeat:false,controller:function(element,view,container,document){for(var i=0;i<element.children.length;i++){let button=document.createElement("button");let template=element.children[i].cloneNode(true);let as=element.getAttribute('data-ls-as');let counter=0;button.type="button";button.innerText="Add";button.classList.add("reverse");button.classList.add("margin-end-small");button.addEventListener('click',function(){container.addNamespace(as,'new-'+counter++);console.log(container.namespaces,container.get(as),as);container.set(as,null,true,true);let child=template.cloneNode(true);view.render(child);element.appendChild(child);element.style.visibility='visible';let inputs=child.querySelectorAll('input,textarea');for(let index=0;index<inputs.length;++index){if(inputs[index].type!=='hidden'){inputs[index].focus();break;}}});element.after(button);}}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-chart",controller:function(element,container,date,document){let wrapper=document.createElement("div");let child=document.createElement("canvas");let sources=element.getAttribute('data-forms-chart');let width=element.getAttribute('data-width')||500;let height=element.getAttribute('data-height')||175;let showXAxis=element.getAttribute('data-show-x-axis')||false;let showYAxis=element.getAttribute('data-show-y-axis')||false;let colors=(element.getAttribute('data-colors')||'blue,green,orange,red').split(',');let themes={'blue':'#29b5d9','green':'#4eb55b','orange':'#fba233','red':'#dc3232','create':'#00b680','read':'#009cde','update':'#696fd7','delete':'#da5d95',};let range={'24h':{hour:'2-digit',minute:'2-digit'},'7d':{year:'numeric',month:'short',day:'2-digit',},'30d':{year:'numeric',month:'short',day:'2-digit',},'90d':{year:'numeric',month:'short',day:'2-digit',}}
|
||||
let ticksCount=5;element.parentNode.insertBefore(wrapper,element.nextSibling);wrapper.classList.add('content');child.width=width;child.height=height;sources=sources.split(',');wrapper.appendChild(child);let chart=null;let check=function(){let config={type:"line",data:{labels:[],datasets:[]},options:{animation:{duration:0},responsive:true,hover:{mode:"nearest",intersect:false},scales:{x:{display:showXAxis},y:{display:showYAxis,min:0,ticks:{count:ticksCount,fontColor:"#8f8f8f"},}},plugins:{title:{display:false,text:"Stats"},legend:{display:false},tooltip:{mode:"index",intersect:false,caretPadding:0},}}};let highest=0;for(let i=0;i<sources.length;i++){let label=sources[i].substring(0,sources[i].indexOf('='));let path=sources[i].substring(sources[i].indexOf('=')+1);let usage=container.get('usage');let data=usage[path];let value=JSON.parse(element.value);config.data.labels[i]=label;config.data.datasets[i]={};config.data.datasets[i].label=label;config.data.datasets[i].borderColor=themes[colors[i]];config.data.datasets[i].backgroundColor=themes[colors[i]]+'36';config.data.datasets[i].borderWidth=2;config.data.datasets[i].data=[0,0,0,0,0,0,0];config.data.datasets[i].fill=true;if(!data){return;}
|
||||
let dateFormat=(value.range&&range[value.range])?range[value.range]:'d F Y';for(let x=0;x<data.length;x++){if(data[x].value>highest){highest=data[x].value;}
|
||||
let dateFormat=(value.range&&range[value.range])?range[value.range]:{year:'numeric',month:'short',day:'2-digit',};for(let x=0;x<data.length;x++){if(data[x].value>highest){highest=data[x].value;}
|
||||
config.data.datasets[i].data[x]=data[x].value;config.data.labels[x]=date.format(dateFormat,data[x].date);}}
|
||||
if(highest==0){config.options.scales.y.ticks.stepSize=1;config.options.scales.y.max=ticksCount;}else{highest=Math.ceil(highest/ticksCount)*ticksCount;config.options.scales.y.ticks.stepSize=highest/ticksCount;config.options.scales.y.max=highest;}
|
||||
if(chart){chart.destroy();}
|
||||
|
|
@ -828,7 +734,8 @@ var file=document.createElement("li");var image=document.createElement("img");im
|
|||
result.bucketId+"/files/"+
|
||||
result.fileId+"/preview?width="+
|
||||
previewWidth+"&height="+
|
||||
previewHeight+"&project="+project+"&mode=admin";image.alt=previewAlt;file.className="file avatar";file.tabIndex=0;file.appendChild(image);preview.appendChild(file);var remove=(function(result){return function(event){render(result.$id);element.value='';};})(result);file.addEventListener("click",remove);file.addEventListener("keypress",remove);element.value=JSON.stringify(result);};input.addEventListener("change",function(){var message=alerts.add({text:labelLoading,class:""},0);var files=input.files;var read=JSON.parse(expression.parse(element.dataset["read"]||"[]"));var write=JSON.parse(expression.parse(element.dataset["write"]||"[]"));sdk.storage.createFile('default','unique()',files[0],read,write).then(function(response){onComplete(message);render({bucketId:response.bucketId,fileId:response.$id});},function(error){alerts.add({text:"An error occurred!",class:""},3000);onComplete(message);});input.disabled=true;});element.addEventListener("change",function(){if(!element.value){return;}
|
||||
previewHeight+"&project="+project+"&mode=admin";image.alt=previewAlt;file.className="file avatar";file.tabIndex=0;file.appendChild(image);preview.appendChild(file);var remove=(function(result){return function(event){render(result.$id);element.value='';};})(result);file.addEventListener("click",remove);file.addEventListener("keypress",remove);element.value=JSON.stringify(result);};input.addEventListener("change",function(){var message=alerts.add({text:labelLoading,class:""},0);var files=input.files;var permissions=JSON.parse(expression.parse(element.dataset["permissions"]||"[]"))
|
||||
sdk.storage.createFile('default','unique()',files[0],permissions).then(function(response){onComplete(message);render({bucketId:response.bucketId,fileId:response.$id});},function(error){alerts.add({text:"An error occurred!",class:""},3000);onComplete(message);});input.disabled=true;});element.addEventListener("change",function(){if(!element.value){return;}
|
||||
render(element.value);wrapper.scrollIntoView();});upload.addEventListener("keypress",function(){input.click();});element.parentNode.insertBefore(wrapper,element);wrapper.appendChild(preview);wrapper.appendChild(progress);wrapper.appendChild(upload);upload.appendChild(input);render(output);if(searchButton){let searchOpen=document.createElement("button");searchOpen.type='button';searchOpen.innerHTML='<i class="icon icon-search"></i> Search';searchOpen.classList.add('reverse');let path=container.scope(searchButton);searchOpen.addEventListener('click',function(){search.selected=element.value;search.path=path;document.dispatchEvent(new CustomEvent("open-file-search",{bubbles:false,cancelable:true}));});wrapper.appendChild(searchOpen);}}});})(window);(function(window){window.ls.container.get("view").add({selector:"data-cookies",controller:function(element,alerts,cookie,env){if(!cookie.get("cookie-alert")){let text=element.dataset["cookies"]||"";alerts.add({text:text,class:"cookie-alert",link:env.HOME+"/policy/cookies",label:'Learn More',callback:function(){cookie.set("cookie-alert","true",365*10);}},0);}}});})(window);(function(window){"use strict";window.ls.view.add({selector:'data-general-copy',repeat:false,controller:function(document,element,alerts){let button=document.createElement("i");button.type="button";button.title="Copy to Clipboard";button.className=element.getAttribute("data-class")||"icon-docs note copy";button.style.cursor="pointer";element.parentNode.insertBefore(button,element.nextSibling);let copy=function(event){window.getSelection().removeAllRanges();let range=document.createRange();range.selectNode(element);window.getSelection().addRange(range);try{document.execCommand("copy");alerts.add({text:"Copied to clipboard",class:""},3000);}catch(err){alerts.add({text:"Failed to copy text ",class:"error"},3000);}
|
||||
window.getSelection().removeAllRanges();};button.addEventListener("click",copy);}});})(window);(function(window){window.ls.container.get("view").add({selector:"data-page-title",repeat:true,controller:function(element,document,expression){document.title=expression.parse(element.getAttribute("data-page-title"))||document.title;}});})(window);(function(window){"use strict";window.ls.view.add({selector:'data-general-scroll-to',repeat:false,controller:function(element,window){let button=window.document.createElement('button');button.className='scroll-to icon-up-dir';button.alt='Back To Top';button.title='Back To Top';button.addEventListener('click',function(){element.scrollIntoView(true,{behavior:'smooth'});button.blur();},false);element.appendChild(button);}});})(window);(function(window){"use strict";window.ls.view.add({selector:'data-general-scroll-direction',repeat:false,controller:function(element,window){let position=0;let check=function(){let direction=window.document.documentElement.scrollTop;if(direction>position){element.classList.remove('scroll-to-top')
|
||||
element.classList.add('scroll-to-bottom')}
|
||||
|
|
|
|||
2
public/dist/styles/default-ltr.css
vendored
2
public/dist/styles/default-ltr.css
vendored
File diff suppressed because one or more lines are too long
2
public/dist/styles/default-rtl.css
vendored
2
public/dist/styles/default-rtl.css
vendored
File diff suppressed because one or more lines are too long
Binary file not shown.
|
Before Width: | Height: | Size: 882 B After Width: | Height: | Size: 1.2 KiB |
BIN
public/images/users/etsy.png
Normal file
BIN
public/images/users/etsy.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.6 KiB |
|
|
@ -1,29 +1,29 @@
|
|||
(function (exports, isomorphicFormData, crossFetch) {
|
||||
'use strict';
|
||||
|
||||
/******************************************************************************
|
||||
Copyright (c) Microsoft Corporation.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
PERFORMANCE OF THIS SOFTWARE.
|
||||
***************************************************************************** */
|
||||
|
||||
function __awaiter(thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
/******************************************************************************
|
||||
Copyright (c) Microsoft Corporation.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
PERFORMANCE OF THIS SOFTWARE.
|
||||
***************************************************************************** */
|
||||
|
||||
function __awaiter(thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
}
|
||||
|
||||
class Service {
|
||||
|
|
@ -49,19 +49,25 @@
|
|||
|
||||
class Query {
|
||||
}
|
||||
Query.equal = (attribute, value) => Query.addQuery(attribute, 'equal', value);
|
||||
Query.notEqual = (attribute, value) => Query.addQuery(attribute, 'notEqual', value);
|
||||
Query.lesser = (attribute, value) => Query.addQuery(attribute, 'lesser', value);
|
||||
Query.lesserEqual = (attribute, value) => Query.addQuery(attribute, 'lesserEqual', value);
|
||||
Query.greater = (attribute, value) => Query.addQuery(attribute, 'greater', value);
|
||||
Query.greaterEqual = (attribute, value) => Query.addQuery(attribute, 'greaterEqual', value);
|
||||
Query.search = (attribute, value) => Query.addQuery(attribute, 'search', value);
|
||||
Query.addQuery = (attribute, oper, value) => value instanceof Array
|
||||
? `${attribute}.${oper}(${value
|
||||
Query.equal = (attribute, value) => Query.addQuery(attribute, "equal", value);
|
||||
Query.notEqual = (attribute, value) => Query.addQuery(attribute, "notEqual", value);
|
||||
Query.lessThan = (attribute, value) => Query.addQuery(attribute, "lessThan", value);
|
||||
Query.lessThanEqual = (attribute, value) => Query.addQuery(attribute, "lessThanEqual", value);
|
||||
Query.greaterThan = (attribute, value) => Query.addQuery(attribute, "greaterThan", value);
|
||||
Query.greaterThanEqual = (attribute, value) => Query.addQuery(attribute, "greaterThanEqual", value);
|
||||
Query.search = (attribute, value) => Query.addQuery(attribute, "search", value);
|
||||
Query.orderDesc = (attribute) => `orderDesc("${attribute}")`;
|
||||
Query.orderAsc = (attribute) => `orderAsc("${attribute}")`;
|
||||
Query.cursorAfter = (documentId) => `cursorAfter("${documentId}")`;
|
||||
Query.cursorBefore = (documentId) => `cursorBefore("${documentId}")`;
|
||||
Query.limit = (limit) => `limit(${limit})`;
|
||||
Query.offset = (offset) => `offset(${offset})`;
|
||||
Query.addQuery = (attribute, method, value) => value instanceof Array
|
||||
? `${method}("${attribute}", [${value
|
||||
.map((v) => Query.parseValues(v))
|
||||
.join(',')})`
|
||||
: `${attribute}.${oper}(${Query.parseValues(value)})`;
|
||||
Query.parseValues = (value) => typeof value === 'string' || value instanceof String
|
||||
.join(",")}])`
|
||||
: `${method}("${attribute}", [${Query.parseValues(value)}])`;
|
||||
Query.parseValues = (value) => typeof value === "string" || value instanceof String
|
||||
? `"${value}"`
|
||||
: `${value}`;
|
||||
|
||||
|
|
@ -1867,13 +1873,12 @@
|
|||
* @param {string} databaseId
|
||||
* @param {string} collectionId
|
||||
* @param {string} name
|
||||
* @param {string} permission
|
||||
* @param {string[]} read
|
||||
* @param {string[]} write
|
||||
* @param {string[]} permissions
|
||||
* @param {boolean} documentSecurity
|
||||
* @throws {AppwriteException}
|
||||
* @returns {Promise}
|
||||
*/
|
||||
createCollection(databaseId, collectionId, name, permission, read, write) {
|
||||
createCollection(databaseId, collectionId, name, permissions, documentSecurity) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
if (typeof databaseId === 'undefined') {
|
||||
throw new AppwriteException('Missing required parameter: "databaseId"');
|
||||
|
|
@ -1884,14 +1889,11 @@
|
|||
if (typeof name === 'undefined') {
|
||||
throw new AppwriteException('Missing required parameter: "name"');
|
||||
}
|
||||
if (typeof permission === 'undefined') {
|
||||
throw new AppwriteException('Missing required parameter: "permission"');
|
||||
if (typeof permissions === 'undefined') {
|
||||
throw new AppwriteException('Missing required parameter: "permissions"');
|
||||
}
|
||||
if (typeof read === 'undefined') {
|
||||
throw new AppwriteException('Missing required parameter: "read"');
|
||||
}
|
||||
if (typeof write === 'undefined') {
|
||||
throw new AppwriteException('Missing required parameter: "write"');
|
||||
if (typeof documentSecurity === 'undefined') {
|
||||
throw new AppwriteException('Missing required parameter: "documentSecurity"');
|
||||
}
|
||||
let path = '/databases/{databaseId}/collections'.replace('{databaseId}', databaseId);
|
||||
let payload = {};
|
||||
|
|
@ -1901,14 +1903,11 @@
|
|||
if (typeof name !== 'undefined') {
|
||||
payload['name'] = name;
|
||||
}
|
||||
if (typeof permission !== 'undefined') {
|
||||
payload['permission'] = permission;
|
||||
if (typeof permissions !== 'undefined') {
|
||||
payload['permissions'] = permissions;
|
||||
}
|
||||
if (typeof read !== 'undefined') {
|
||||
payload['read'] = read;
|
||||
}
|
||||
if (typeof write !== 'undefined') {
|
||||
payload['write'] = write;
|
||||
if (typeof documentSecurity !== 'undefined') {
|
||||
payload['documentSecurity'] = documentSecurity;
|
||||
}
|
||||
const uri = new URL(this.client.config.endpoint + path);
|
||||
return yield this.client.call('post', uri, {
|
||||
|
|
@ -1951,14 +1950,13 @@
|
|||
* @param {string} databaseId
|
||||
* @param {string} collectionId
|
||||
* @param {string} name
|
||||
* @param {string} permission
|
||||
* @param {string[]} read
|
||||
* @param {string[]} write
|
||||
* @param {boolean} documentSecurity
|
||||
* @param {string[]} permissions
|
||||
* @param {boolean} enabled
|
||||
* @throws {AppwriteException}
|
||||
* @returns {Promise}
|
||||
*/
|
||||
updateCollection(databaseId, collectionId, name, permission, read, write, enabled) {
|
||||
updateCollection(databaseId, collectionId, name, documentSecurity, permissions, enabled) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
if (typeof databaseId === 'undefined') {
|
||||
throw new AppwriteException('Missing required parameter: "databaseId"');
|
||||
|
|
@ -1969,22 +1967,19 @@
|
|||
if (typeof name === 'undefined') {
|
||||
throw new AppwriteException('Missing required parameter: "name"');
|
||||
}
|
||||
if (typeof permission === 'undefined') {
|
||||
throw new AppwriteException('Missing required parameter: "permission"');
|
||||
if (typeof documentSecurity === 'undefined') {
|
||||
throw new AppwriteException('Missing required parameter: "documentSecurity"');
|
||||
}
|
||||
let path = '/databases/{databaseId}/collections/{collectionId}'.replace('{databaseId}', databaseId).replace('{collectionId}', collectionId);
|
||||
let payload = {};
|
||||
if (typeof name !== 'undefined') {
|
||||
payload['name'] = name;
|
||||
}
|
||||
if (typeof permission !== 'undefined') {
|
||||
payload['permission'] = permission;
|
||||
if (typeof permissions !== 'undefined') {
|
||||
payload['permissions'] = permissions;
|
||||
}
|
||||
if (typeof read !== 'undefined') {
|
||||
payload['read'] = read;
|
||||
}
|
||||
if (typeof write !== 'undefined') {
|
||||
payload['write'] = write;
|
||||
if (typeof documentSecurity !== 'undefined') {
|
||||
payload['documentSecurity'] = documentSecurity;
|
||||
}
|
||||
if (typeof enabled !== 'undefined') {
|
||||
payload['enabled'] = enabled;
|
||||
|
|
@ -2096,6 +2091,53 @@
|
|||
}, payload);
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Create DateTime Attribute
|
||||
*
|
||||
*
|
||||
* @param {string} databaseId
|
||||
* @param {string} collectionId
|
||||
* @param {string} key
|
||||
* @param {boolean} required
|
||||
* @param {string} xdefault
|
||||
* @param {boolean} array
|
||||
* @throws {AppwriteException}
|
||||
* @returns {Promise}
|
||||
*/
|
||||
createDatetimeAttribute(databaseId, collectionId, key, required, xdefault, array) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
if (typeof databaseId === 'undefined') {
|
||||
throw new AppwriteException('Missing required parameter: "databaseId"');
|
||||
}
|
||||
if (typeof collectionId === 'undefined') {
|
||||
throw new AppwriteException('Missing required parameter: "collectionId"');
|
||||
}
|
||||
if (typeof key === 'undefined') {
|
||||
throw new AppwriteException('Missing required parameter: "key"');
|
||||
}
|
||||
if (typeof required === 'undefined') {
|
||||
throw new AppwriteException('Missing required parameter: "required"');
|
||||
}
|
||||
let path = '/databases/{databaseId}/collections/{collectionId}/attributes/datetime'.replace('{databaseId}', databaseId).replace('{collectionId}', collectionId);
|
||||
let payload = {};
|
||||
if (typeof key !== 'undefined') {
|
||||
payload['key'] = key;
|
||||
}
|
||||
if (typeof required !== 'undefined') {
|
||||
payload['required'] = required;
|
||||
}
|
||||
if (typeof xdefault !== 'undefined') {
|
||||
payload['default'] = xdefault;
|
||||
}
|
||||
if (typeof array !== 'undefined') {
|
||||
payload['array'] = array;
|
||||
}
|
||||
const uri = new URL(this.client.config.endpoint + path);
|
||||
return yield this.client.call('post', uri, {
|
||||
'content-type': 'application/json',
|
||||
}, payload);
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Create Email Attribute
|
||||
*
|
||||
|
|
@ -2596,12 +2638,11 @@
|
|||
* @param {string} collectionId
|
||||
* @param {string} documentId
|
||||
* @param {Omit<Document, keyof Models.Document>} data
|
||||
* @param {string[]} read
|
||||
* @param {string[]} write
|
||||
* @param {string[]} permissions
|
||||
* @throws {AppwriteException}
|
||||
* @returns {Promise}
|
||||
*/
|
||||
createDocument(databaseId, collectionId, documentId, data, read, write) {
|
||||
createDocument(databaseId, collectionId, documentId, data, permissions) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
if (typeof databaseId === 'undefined') {
|
||||
throw new AppwriteException('Missing required parameter: "databaseId"');
|
||||
|
|
@ -2623,11 +2664,8 @@
|
|||
if (typeof data !== 'undefined') {
|
||||
payload['data'] = data;
|
||||
}
|
||||
if (typeof read !== 'undefined') {
|
||||
payload['read'] = read;
|
||||
}
|
||||
if (typeof write !== 'undefined') {
|
||||
payload['write'] = write;
|
||||
if (typeof permissions !== 'undefined') {
|
||||
payload['permissions'] = permissions;
|
||||
}
|
||||
const uri = new URL(this.client.config.endpoint + path);
|
||||
return yield this.client.call('post', uri, {
|
||||
|
|
@ -2676,12 +2714,11 @@
|
|||
* @param {string} collectionId
|
||||
* @param {string} documentId
|
||||
* @param {Partial<Omit<Document, keyof Models.Document>>} data
|
||||
* @param {string[]} read
|
||||
* @param {string[]} write
|
||||
* @param {string[]} permissions
|
||||
* @throws {AppwriteException}
|
||||
* @returns {Promise}
|
||||
*/
|
||||
updateDocument(databaseId, collectionId, documentId, data, read, write) {
|
||||
updateDocument(databaseId, collectionId, documentId, data, permissions) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
if (typeof databaseId === 'undefined') {
|
||||
throw new AppwriteException('Missing required parameter: "databaseId"');
|
||||
|
|
@ -2697,11 +2734,8 @@
|
|||
if (typeof data !== 'undefined') {
|
||||
payload['data'] = data;
|
||||
}
|
||||
if (typeof read !== 'undefined') {
|
||||
payload['read'] = read;
|
||||
}
|
||||
if (typeof write !== 'undefined') {
|
||||
payload['write'] = write;
|
||||
if (typeof permissions !== 'undefined') {
|
||||
payload['permissions'] = permissions;
|
||||
}
|
||||
const uri = new URL(this.client.config.endpoint + path);
|
||||
return yield this.client.call('patch', uri, {
|
||||
|
|
@ -4436,7 +4470,7 @@
|
|||
* @param {string} projectId
|
||||
* @param {string} name
|
||||
* @param {string[]} scopes
|
||||
* @param {number} expire
|
||||
* @param {string} expire
|
||||
* @throws {AppwriteException}
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
|
@ -4501,7 +4535,7 @@
|
|||
* @param {string} keyId
|
||||
* @param {string} name
|
||||
* @param {string[]} scopes
|
||||
* @param {number} expire
|
||||
* @param {string} expire
|
||||
* @throws {AppwriteException}
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
|
@ -5089,9 +5123,8 @@
|
|||
*
|
||||
* @param {string} bucketId
|
||||
* @param {string} name
|
||||
* @param {string} permission
|
||||
* @param {string[]} read
|
||||
* @param {string[]} write
|
||||
* @param {boolean} fileSecurity
|
||||
* @param {string[]} permissions
|
||||
* @param {boolean} enabled
|
||||
* @param {number} maximumFileSize
|
||||
* @param {string[]} allowedFileExtensions
|
||||
|
|
@ -5100,7 +5133,7 @@
|
|||
* @throws {AppwriteException}
|
||||
* @returns {Promise}
|
||||
*/
|
||||
createBucket(bucketId, name, permission, read, write, enabled, maximumFileSize, allowedFileExtensions, encryption, antivirus) {
|
||||
createBucket(bucketId, name, fileSecurity, permissions, enabled, maximumFileSize, allowedFileExtensions, encryption, antivirus) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
if (typeof bucketId === 'undefined') {
|
||||
throw new AppwriteException('Missing required parameter: "bucketId"');
|
||||
|
|
@ -5108,8 +5141,8 @@
|
|||
if (typeof name === 'undefined') {
|
||||
throw new AppwriteException('Missing required parameter: "name"');
|
||||
}
|
||||
if (typeof permission === 'undefined') {
|
||||
throw new AppwriteException('Missing required parameter: "permission"');
|
||||
if (typeof fileSecurity === 'undefined') {
|
||||
throw new AppwriteException('Missing required parameter: "fileSecurity"');
|
||||
}
|
||||
let path = '/storage/buckets';
|
||||
let payload = {};
|
||||
|
|
@ -5119,14 +5152,11 @@
|
|||
if (typeof name !== 'undefined') {
|
||||
payload['name'] = name;
|
||||
}
|
||||
if (typeof permission !== 'undefined') {
|
||||
payload['permission'] = permission;
|
||||
if (typeof permissions !== 'undefined') {
|
||||
payload['permissions'] = permissions;
|
||||
}
|
||||
if (typeof read !== 'undefined') {
|
||||
payload['read'] = read;
|
||||
}
|
||||
if (typeof write !== 'undefined') {
|
||||
payload['write'] = write;
|
||||
if (typeof fileSecurity !== 'undefined') {
|
||||
payload['fileSecurity'] = fileSecurity;
|
||||
}
|
||||
if (typeof enabled !== 'undefined') {
|
||||
payload['enabled'] = enabled;
|
||||
|
|
@ -5179,9 +5209,8 @@
|
|||
*
|
||||
* @param {string} bucketId
|
||||
* @param {string} name
|
||||
* @param {string} permission
|
||||
* @param {string[]} read
|
||||
* @param {string[]} write
|
||||
* @param {boolean} fileSecurity
|
||||
* @param {string[]} permissions
|
||||
* @param {boolean} enabled
|
||||
* @param {number} maximumFileSize
|
||||
* @param {string[]} allowedFileExtensions
|
||||
|
|
@ -5190,7 +5219,7 @@
|
|||
* @throws {AppwriteException}
|
||||
* @returns {Promise}
|
||||
*/
|
||||
updateBucket(bucketId, name, permission, read, write, enabled, maximumFileSize, allowedFileExtensions, encryption, antivirus) {
|
||||
updateBucket(bucketId, name, fileSecurity, permissions, enabled, maximumFileSize, allowedFileExtensions, encryption, antivirus) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
if (typeof bucketId === 'undefined') {
|
||||
throw new AppwriteException('Missing required parameter: "bucketId"');
|
||||
|
|
@ -5198,22 +5227,19 @@
|
|||
if (typeof name === 'undefined') {
|
||||
throw new AppwriteException('Missing required parameter: "name"');
|
||||
}
|
||||
if (typeof permission === 'undefined') {
|
||||
throw new AppwriteException('Missing required parameter: "permission"');
|
||||
if (typeof fileSecurity === 'undefined') {
|
||||
throw new AppwriteException('Missing required parameter: "fileSecurity"');
|
||||
}
|
||||
let path = '/storage/buckets/{bucketId}'.replace('{bucketId}', bucketId);
|
||||
let payload = {};
|
||||
if (typeof name !== 'undefined') {
|
||||
payload['name'] = name;
|
||||
}
|
||||
if (typeof permission !== 'undefined') {
|
||||
payload['permission'] = permission;
|
||||
if (typeof permissions !== 'undefined') {
|
||||
payload['permissions'] = permissions;
|
||||
}
|
||||
if (typeof read !== 'undefined') {
|
||||
payload['read'] = read;
|
||||
}
|
||||
if (typeof write !== 'undefined') {
|
||||
payload['write'] = write;
|
||||
if (typeof fileSecurity !== 'undefined') {
|
||||
payload['fileSecurity'] = fileSecurity;
|
||||
}
|
||||
if (typeof enabled !== 'undefined') {
|
||||
payload['enabled'] = enabled;
|
||||
|
|
@ -5331,12 +5357,11 @@
|
|||
* @param {string} bucketId
|
||||
* @param {string} fileId
|
||||
* @param {File} file
|
||||
* @param {string[]} read
|
||||
* @param {string[]} write
|
||||
* @param {string[]} permissions
|
||||
* @throws {AppwriteException}
|
||||
* @returns {Promise}
|
||||
*/
|
||||
createFile(bucketId, fileId, file, read, write, onProgress = (progress) => { }) {
|
||||
createFile(bucketId, fileId, file, permissions, onProgress = (progress) => { }) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
if (typeof bucketId === 'undefined') {
|
||||
throw new AppwriteException('Missing required parameter: "bucketId"');
|
||||
|
|
@ -5355,11 +5380,8 @@
|
|||
if (typeof file !== 'undefined') {
|
||||
payload['file'] = file;
|
||||
}
|
||||
if (typeof read !== 'undefined') {
|
||||
payload['read'] = read;
|
||||
}
|
||||
if (typeof write !== 'undefined') {
|
||||
payload['write'] = write;
|
||||
if (typeof permissions !== 'undefined') {
|
||||
payload['permissions'] = permissions;
|
||||
}
|
||||
const uri = new URL(this.client.config.endpoint + path);
|
||||
if (!(file instanceof File)) {
|
||||
|
|
@ -5447,12 +5469,11 @@
|
|||
*
|
||||
* @param {string} bucketId
|
||||
* @param {string} fileId
|
||||
* @param {string[]} read
|
||||
* @param {string[]} write
|
||||
* @param {string[]} permissions
|
||||
* @throws {AppwriteException}
|
||||
* @returns {Promise}
|
||||
*/
|
||||
updateFile(bucketId, fileId, read, write) {
|
||||
updateFile(bucketId, fileId, permissions) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
if (typeof bucketId === 'undefined') {
|
||||
throw new AppwriteException('Missing required parameter: "bucketId"');
|
||||
|
|
@ -5462,11 +5483,8 @@
|
|||
}
|
||||
let path = '/storage/buckets/{bucketId}/files/{fileId}'.replace('{bucketId}', bucketId).replace('{fileId}', fileId);
|
||||
let payload = {};
|
||||
if (typeof read !== 'undefined') {
|
||||
payload['read'] = read;
|
||||
}
|
||||
if (typeof write !== 'undefined') {
|
||||
payload['write'] = write;
|
||||
if (typeof permissions !== 'undefined') {
|
||||
payload['permissions'] = permissions;
|
||||
}
|
||||
const uri = new URL(this.client.config.endpoint + path);
|
||||
return yield this.client.call('put', uri, {
|
||||
|
|
@ -7042,6 +7060,57 @@
|
|||
}
|
||||
}
|
||||
|
||||
class Permission {
|
||||
}
|
||||
Permission.read = (role) => {
|
||||
return `read("${role}")`;
|
||||
};
|
||||
Permission.write = (role) => {
|
||||
return `write("${role}")`;
|
||||
};
|
||||
Permission.create = (role) => {
|
||||
return `create("${role}")`;
|
||||
};
|
||||
Permission.update = (role) => {
|
||||
return `update("${role}")`;
|
||||
};
|
||||
Permission.delete = (role) => {
|
||||
return `delete("${role}")`;
|
||||
};
|
||||
|
||||
class Role {
|
||||
static any() {
|
||||
return 'any';
|
||||
}
|
||||
static user(id) {
|
||||
return `user:${id}`;
|
||||
}
|
||||
static users() {
|
||||
return 'users';
|
||||
}
|
||||
static guests() {
|
||||
return 'guests';
|
||||
}
|
||||
static team(id, role = '') {
|
||||
if (role === '') {
|
||||
return `team:${id}`;
|
||||
}
|
||||
return `team:${id}/${role}`;
|
||||
}
|
||||
static status(status) {
|
||||
return `status:${status}`;
|
||||
}
|
||||
}
|
||||
|
||||
class ID {
|
||||
static custom(id) {
|
||||
return id;
|
||||
}
|
||||
static unique() {
|
||||
return 'unique()';
|
||||
}
|
||||
}
|
||||
|
||||
exports.Account = Account;
|
||||
exports.AppwriteException = AppwriteException;
|
||||
exports.Avatars = Avatars;
|
||||
|
|
@ -7049,9 +7118,12 @@
|
|||
exports.Databases = Databases;
|
||||
exports.Functions = Functions;
|
||||
exports.Health = Health;
|
||||
exports.ID = ID;
|
||||
exports.Locale = Locale;
|
||||
exports.Permission = Permission;
|
||||
exports.Projects = Projects;
|
||||
exports.Query = Query;
|
||||
exports.Role = Role;
|
||||
exports.Storage = Storage;
|
||||
exports.Teams = Teams;
|
||||
exports.Users = Users;
|
||||
|
|
|
|||
|
|
@ -28,19 +28,33 @@ window.ls.filter
|
|||
$value = parseInt($value);
|
||||
return !Number.isNaN($value) ? $value.toLocaleString() : "";
|
||||
})
|
||||
.add("date", function ($value, date) {
|
||||
return $value ? date.format("Y-m-d", $value) : "";
|
||||
})
|
||||
.add("dateTime", function ($value, date) {
|
||||
return $value ? date.format("Y-m-d H:i", $value) : "";
|
||||
return $value ? date.format({
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
}, $value) : "";
|
||||
})
|
||||
.add("dateText", function ($value, date) {
|
||||
return $value ? date.format("d M Y", $value) : "";
|
||||
.add("date", function ($value, date) {
|
||||
return $value ? date.format({
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: '2-digit',
|
||||
}, $value) : "";
|
||||
})
|
||||
.add("timeSince", function ($value) {
|
||||
$value = $value * 1000;
|
||||
$value = new Date($value).getTime();
|
||||
|
||||
let seconds = Math.floor((Date.now() - $value) / 1000);
|
||||
/**
|
||||
* Adapt to timezone UTC.
|
||||
*/
|
||||
let now = new Date();
|
||||
now.setMinutes(now.getMinutes() + now.getTimezoneOffset());
|
||||
|
||||
let timestamp = new Date(now.toISOString()).getTime();
|
||||
let seconds = Math.floor((timestamp - $value) / 1000);
|
||||
let unit = "second";
|
||||
let direction = "ago";
|
||||
|
||||
|
|
|
|||
122
public/scripts/permissions-matrix.js
Normal file
122
public/scripts/permissions-matrix.js
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
(function (window) {
|
||||
document.addEventListener('alpine:init', () => {
|
||||
Alpine.data('permissionsMatrix', () => ({
|
||||
permissions: [],
|
||||
rawPermissions: [],
|
||||
load(permissions) {
|
||||
if (permissions === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.rawPermissions = permissions;
|
||||
|
||||
permissions.map(p => {
|
||||
let {type, role} = this.parsePermission(p);
|
||||
type = this.parseInputPermission(type);
|
||||
|
||||
let index = -1;
|
||||
let existing = this.permissions.find((p, idx) => {
|
||||
if (p.role === role) {
|
||||
index = idx;
|
||||
return true;
|
||||
}
|
||||
})
|
||||
if (existing === undefined) {
|
||||
this.permissions.push({
|
||||
role,
|
||||
[type]: true,
|
||||
});
|
||||
}
|
||||
if (index !== -1) {
|
||||
existing[type] = true;
|
||||
this.permissions[index] = existing;
|
||||
}
|
||||
});
|
||||
},
|
||||
addPermission(formId, role, permissions) {
|
||||
if (!document.getElementById(formId).reportValidity()) {
|
||||
return;
|
||||
}
|
||||
Object.entries(permissions).forEach(entry => {
|
||||
let [type, enabled] = entry;
|
||||
type = this.parseOutputPermission(type);
|
||||
if (enabled) {
|
||||
this.rawPermissions.push(this.buildPermission(type, role));
|
||||
}
|
||||
});
|
||||
this.permissions.push({
|
||||
role,
|
||||
...permissions,
|
||||
});
|
||||
this.reset();
|
||||
},
|
||||
updatePermission(index) {
|
||||
// Because the x-model does not update before the click event,
|
||||
// we setTimeout to give Alpine enough time to update the model.
|
||||
setTimeout(() => {
|
||||
const permission = this.permissions[index];
|
||||
|
||||
Object.keys(permission).forEach(key => {
|
||||
if (key === 'role') {
|
||||
return;
|
||||
}
|
||||
const parsedKey = this.parseOutputPermission(key);
|
||||
const permissionString = this.buildPermission(parsedKey, permission.role);
|
||||
if (permission[key]) {
|
||||
if (!this.rawPermissions.includes(permissionString)) {
|
||||
this.rawPermissions.push(permissionString);
|
||||
}
|
||||
} else {
|
||||
this.rawPermissions = this.rawPermissions.filter(p => {
|
||||
return !p.includes(permissionString);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
removePermission(index) {
|
||||
let row = this.permissions.splice(index, 1);
|
||||
if (row.length === 1) {
|
||||
this.rawPermissions = this.rawPermissions.filter(p => !p.includes(row[0].role));
|
||||
}
|
||||
},
|
||||
parsePermission(permission) {
|
||||
let parts = permission.split('(');
|
||||
let type = parts[0];
|
||||
let role = parts[1]
|
||||
.replace(')', '')
|
||||
.replace(' ', '')
|
||||
.replaceAll('"', '');
|
||||
return {type, role};
|
||||
},
|
||||
buildPermission(type, role) {
|
||||
return `${type}("${role}")`
|
||||
},
|
||||
parseInputPermission(key) {
|
||||
// Can't bind to a property named delete
|
||||
if (key === 'delete') {
|
||||
return 'xdelete';
|
||||
}
|
||||
return key;
|
||||
},
|
||||
parseOutputPermission(key) {
|
||||
// Can't bind to a property named delete
|
||||
if (key === 'xdelete') {
|
||||
return 'delete';
|
||||
}
|
||||
return key;
|
||||
}
|
||||
}));
|
||||
Alpine.data('permissionsRow', () => ({
|
||||
role: '',
|
||||
read: false,
|
||||
create: false,
|
||||
update: false,
|
||||
xdelete: false,
|
||||
reset() {
|
||||
this.role = '';
|
||||
this.read = this.create = this.update = this.xdelete = false;
|
||||
}
|
||||
}));
|
||||
});
|
||||
})(window);
|
||||
|
|
@ -2,603 +2,19 @@
|
|||
"use strict";
|
||||
|
||||
window.ls.container.set('date', function () {
|
||||
function format (format, timestamp) {
|
||||
// discuss at: http://locutus.io/php/date/
|
||||
// original by: Carlos R. L. Rodrigues (http://www.jsfromhell.com)
|
||||
// original by: gettimeofday
|
||||
// parts by: Peter-Paul Koch (http://www.quirksmode.org/js/beat.html)
|
||||
// improved by: Kevin van Zonneveld (http://kvz.io)
|
||||
// improved by: MeEtc (http://yass.meetcweb.com)
|
||||
// improved by: Brad Touesnard
|
||||
// improved by: Tim Wiel
|
||||
// improved by: Bryan Elliott
|
||||
// improved by: David Randall
|
||||
// improved by: Theriault (https://github.com/Theriault)
|
||||
// improved by: Theriault (https://github.com/Theriault)
|
||||
// improved by: Brett Zamir (http://brett-zamir.me)
|
||||
// improved by: Theriault (https://github.com/Theriault)
|
||||
// improved by: Thomas Beaucourt (http://www.webapp.fr)
|
||||
// improved by: JT
|
||||
// improved by: Theriault (https://github.com/Theriault)
|
||||
// improved by: Rafał Kukawski (http://blog.kukawski.pl)
|
||||
// improved by: Theriault (https://github.com/Theriault)
|
||||
// input by: Brett Zamir (http://brett-zamir.me)
|
||||
// input by: majak
|
||||
// input by: Alex
|
||||
// input by: Martin
|
||||
// input by: Alex Wilson
|
||||
// input by: Haravikk
|
||||
// bugfixed by: Kevin van Zonneveld (http://kvz.io)
|
||||
// bugfixed by: majak
|
||||
// bugfixed by: Kevin van Zonneveld (http://kvz.io)
|
||||
// bugfixed by: Brett Zamir (http://brett-zamir.me)
|
||||
// bugfixed by: omid (http://locutus.io/php/380:380#comment_137122)
|
||||
// bugfixed by: Chris (http://www.devotis.nl/)
|
||||
// note 1: Uses global: locutus to store the default timezone
|
||||
// note 1: Although the function potentially allows timezone info
|
||||
// note 1: (see notes), it currently does not set
|
||||
// note 1: per a timezone specified by date_default_timezone_set(). Implementers might use
|
||||
// note 1: $locutus.currentTimezoneOffset and
|
||||
// note 1: $locutus.currentTimezoneDST set by that function
|
||||
// note 1: in order to adjust the dates in this function
|
||||
// note 1: (or our other date functions!) accordingly
|
||||
// example 1: date('H:m:s \\m \\i\\s \\m\\o\\n\\t\\h', 1062402400)
|
||||
// returns 1: '07:09:40 m is month'
|
||||
// example 2: date('F j, Y, g:i a', 1062462400)
|
||||
// returns 2: 'September 2, 2003, 12:26 am'
|
||||
// example 3: date('Y W o', 1062462400)
|
||||
// returns 3: '2003 36 2003'
|
||||
// example 4: var $x = date('Y m d', (new Date()).getTime() / 1000)
|
||||
// example 4: $x = $x + ''
|
||||
// example 4: var $result = $x.length // 2009 01 09
|
||||
// returns 4: 10
|
||||
// example 5: date('W', 1104534000)
|
||||
// returns 5: '52'
|
||||
// example 6: date('B t', 1104534000)
|
||||
// returns 6: '999 31'
|
||||
// example 7: date('W U', 1293750000.82); // 2010-12-31
|
||||
// returns 7: '52 1293750000'
|
||||
// example 8: date('W', 1293836400); // 2011-01-01
|
||||
// returns 8: '52'
|
||||
// example 9: date('W Y-m-d', 1293974054); // 2011-01-02
|
||||
// returns 9: '52 2011-01-02'
|
||||
// test: skip-1 skip-2 skip-5
|
||||
|
||||
var jsdate, f
|
||||
// Keep this here (works, but for code commented-out below for file size reasons)
|
||||
// var tal= [];
|
||||
var txtWords = [
|
||||
'Sun', 'Mon', 'Tues', 'Wednes', 'Thurs', 'Fri', 'Satur',
|
||||
'January', 'February', 'March', 'April', 'May', 'June',
|
||||
'July', 'August', 'September', 'October', 'November', 'December'
|
||||
]
|
||||
// trailing backslash -> (dropped)
|
||||
// a backslash followed by any character (including backslash) -> the character
|
||||
// empty string -> empty string
|
||||
var formatChr = /\\?(.?)/gi
|
||||
var formatChrCb = function (t, s) {
|
||||
return f[t] ? f[t]() : s
|
||||
}
|
||||
var _pad = function (n, c) {
|
||||
n = String(n)
|
||||
while (n.length < c) {
|
||||
n = '0' + n
|
||||
}
|
||||
return n
|
||||
}
|
||||
f = {
|
||||
// Day
|
||||
d: function () {
|
||||
// Day of month w/leading 0; 01..31
|
||||
return _pad(f.j(), 2)
|
||||
},
|
||||
D: function () {
|
||||
// Shorthand day name; Mon...Sun
|
||||
return f.l()
|
||||
.slice(0, 3)
|
||||
},
|
||||
j: function () {
|
||||
// Day of month; 1..31
|
||||
return jsdate.getDate()
|
||||
},
|
||||
l: function () {
|
||||
// Full day name; Monday...Sunday
|
||||
return txtWords[f.w()] + 'day'
|
||||
},
|
||||
N: function () {
|
||||
// ISO-8601 day of week; 1[Mon]..7[Sun]
|
||||
return f.w() || 7
|
||||
},
|
||||
S: function () {
|
||||
// Ordinal suffix for day of month; st, nd, rd, th
|
||||
var j = f.j()
|
||||
var i = j % 10
|
||||
if (i <= 3 && parseInt((j % 100) / 10, 10) === 1) {
|
||||
i = 0
|
||||
}
|
||||
return ['st', 'nd', 'rd'][i - 1] || 'th'
|
||||
},
|
||||
w: function () {
|
||||
// Day of week; 0[Sun]..6[Sat]
|
||||
return jsdate.getDay()
|
||||
},
|
||||
z: function () {
|
||||
// Day of year; 0..365
|
||||
var a = new Date(f.Y(), f.n() - 1, f.j())
|
||||
var b = new Date(f.Y(), 0, 1)
|
||||
return Math.round((a - b) / 864e5)
|
||||
},
|
||||
|
||||
// Week
|
||||
W: function () {
|
||||
// ISO-8601 week number
|
||||
var a = new Date(f.Y(), f.n() - 1, f.j() - f.N() + 3)
|
||||
var b = new Date(a.getFullYear(), 0, 4)
|
||||
return _pad(1 + Math.round((a - b) / 864e5 / 7), 2)
|
||||
},
|
||||
|
||||
// Month
|
||||
F: function () {
|
||||
// Full month name; January...December
|
||||
return txtWords[6 + f.n()]
|
||||
},
|
||||
m: function () {
|
||||
// Month w/leading 0; 01...12
|
||||
return _pad(f.n(), 2)
|
||||
},
|
||||
M: function () {
|
||||
// Shorthand month name; Jan...Dec
|
||||
return f.F()
|
||||
.slice(0, 3)
|
||||
},
|
||||
n: function () {
|
||||
// Month; 1...12
|
||||
return jsdate.getMonth() + 1
|
||||
},
|
||||
t: function () {
|
||||
// Days in month; 28...31
|
||||
return (new Date(f.Y(), f.n(), 0))
|
||||
.getDate()
|
||||
},
|
||||
|
||||
// Year
|
||||
L: function () {
|
||||
// Is leap year?; 0 or 1
|
||||
var j = f.Y()
|
||||
return j % 4 === 0 & j % 100 !== 0 | j % 400 === 0
|
||||
},
|
||||
o: function () {
|
||||
// ISO-8601 year
|
||||
var n = f.n()
|
||||
var W = f.W()
|
||||
var Y = f.Y()
|
||||
return Y + (n === 12 && W < 9 ? 1 : n === 1 && W > 9 ? -1 : 0)
|
||||
},
|
||||
Y: function () {
|
||||
// Full year; e.g. 1980...2010
|
||||
return jsdate.getFullYear()
|
||||
},
|
||||
y: function () {
|
||||
// Last two digits of year; 00...99
|
||||
return f.Y()
|
||||
.toString()
|
||||
.slice(-2)
|
||||
},
|
||||
|
||||
// Time
|
||||
a: function () {
|
||||
// am or pm
|
||||
return jsdate.getHours() > 11 ? 'pm' : 'am'
|
||||
},
|
||||
A: function () {
|
||||
// AM or PM
|
||||
return f.a()
|
||||
.toUpperCase()
|
||||
},
|
||||
B: function () {
|
||||
// Swatch Internet time; 000..999
|
||||
var H = jsdate.getUTCHours() * 36e2
|
||||
// Hours
|
||||
var i = jsdate.getUTCMinutes() * 60
|
||||
// Minutes
|
||||
// Seconds
|
||||
var s = jsdate.getUTCSeconds()
|
||||
return _pad(Math.floor((H + i + s + 36e2) / 86.4) % 1e3, 3)
|
||||
},
|
||||
g: function () {
|
||||
// 12-Hours; 1..12
|
||||
return f.G() % 12 || 12
|
||||
},
|
||||
G: function () {
|
||||
// 24-Hours; 0..23
|
||||
return jsdate.getHours()
|
||||
},
|
||||
h: function () {
|
||||
// 12-Hours w/leading 0; 01..12
|
||||
return _pad(f.g(), 2)
|
||||
},
|
||||
H: function () {
|
||||
// 24-Hours w/leading 0; 00..23
|
||||
return _pad(f.G(), 2)
|
||||
},
|
||||
i: function () {
|
||||
// Minutes w/leading 0; 00..59
|
||||
return _pad(jsdate.getMinutes(), 2)
|
||||
},
|
||||
s: function () {
|
||||
// Seconds w/leading 0; 00..59
|
||||
return _pad(jsdate.getSeconds(), 2)
|
||||
},
|
||||
u: function () {
|
||||
// Microseconds; 000000-999000
|
||||
return _pad(jsdate.getMilliseconds() * 1000, 6)
|
||||
},
|
||||
|
||||
// Timezone
|
||||
e: function () {
|
||||
// Timezone identifier; e.g. Atlantic/Azores, ...
|
||||
// The following works, but requires inclusion of the very large
|
||||
// timezone_abbreviations_list() function.
|
||||
/* return that.date_default_timezone_get();
|
||||
*/
|
||||
var msg = 'Not supported (see source code of date() for timezone on how to add support)'
|
||||
throw new Error(msg)
|
||||
},
|
||||
I: function () {
|
||||
// DST observed?; 0 or 1
|
||||
// Compares Jan 1 minus Jan 1 UTC to Jul 1 minus Jul 1 UTC.
|
||||
// If they are not equal, then DST is observed.
|
||||
var a = new Date(f.Y(), 0)
|
||||
// Jan 1
|
||||
var c = Date.UTC(f.Y(), 0)
|
||||
// Jan 1 UTC
|
||||
var b = new Date(f.Y(), 6)
|
||||
// Jul 1
|
||||
// Jul 1 UTC
|
||||
var d = Date.UTC(f.Y(), 6)
|
||||
return ((a - c) !== (b - d)) ? 1 : 0
|
||||
},
|
||||
O: function () {
|
||||
// Difference to GMT in hour format; e.g. +0200
|
||||
var tzo = jsdate.getTimezoneOffset()
|
||||
var a = Math.abs(tzo)
|
||||
return (tzo > 0 ? '-' : '+') + _pad(Math.floor(a / 60) * 100 + a % 60, 4)
|
||||
},
|
||||
P: function () {
|
||||
// Difference to GMT w/colon; e.g. +02:00
|
||||
var O = f.O()
|
||||
return (O.substr(0, 3) + ':' + O.substr(3, 2))
|
||||
},
|
||||
T: function () {
|
||||
// The following works, but requires inclusion of the very
|
||||
// large timezone_abbreviations_list() function.
|
||||
/* var abbr, i, os, _default;
|
||||
if (!tal.length) {
|
||||
tal = that.timezone_abbreviations_list();
|
||||
}
|
||||
if ($locutus && $locutus.default_timezone) {
|
||||
_default = $locutus.default_timezone;
|
||||
for (abbr in tal) {
|
||||
for (i = 0; i < tal[abbr].length; i++) {
|
||||
if (tal[abbr][i].timezone_id === _default) {
|
||||
return abbr.toUpperCase();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (abbr in tal) {
|
||||
for (i = 0; i < tal[abbr].length; i++) {
|
||||
os = -jsdate.getTimezoneOffset() * 60;
|
||||
if (tal[abbr][i].offset === os) {
|
||||
return abbr.toUpperCase();
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
return 'UTC'
|
||||
},
|
||||
Z: function () {
|
||||
// Timezone offset in seconds (-43200...50400)
|
||||
return -jsdate.getTimezoneOffset() * 60
|
||||
},
|
||||
|
||||
// Full Date/Time
|
||||
c: function () {
|
||||
// ISO-8601 date.
|
||||
return 'Y-m-d\\TH:i:sP'.replace(formatChr, formatChrCb)
|
||||
},
|
||||
r: function () {
|
||||
// RFC 2822
|
||||
return 'D, d M Y H:i:s O'.replace(formatChr, formatChrCb)
|
||||
},
|
||||
U: function () {
|
||||
// Seconds since UNIX epoch
|
||||
return jsdate / 1000 | 0
|
||||
}
|
||||
function format(format, datetime) {
|
||||
if (!datetime) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var _date = function (format, timestamp) {
|
||||
jsdate = (timestamp === undefined ? new Date() // Not provided
|
||||
: (timestamp instanceof Date) ? new Date(timestamp) // JS Date()
|
||||
: new Date(timestamp * 1000) // Unix timestamp (auto-convert to int)
|
||||
)
|
||||
return format.replace(formatChr, formatChrCb)
|
||||
}
|
||||
|
||||
return _date(format, timestamp)
|
||||
}
|
||||
|
||||
function strtotime (text, now) {
|
||||
// discuss at: http://locutus.io/php/strtotime/
|
||||
// original by: Caio Ariede (http://caioariede.com)
|
||||
// improved by: Kevin van Zonneveld (http://kvz.io)
|
||||
// improved by: Caio Ariede (http://caioariede.com)
|
||||
// improved by: A. Matías Quezada (http://amatiasq.com)
|
||||
// improved by: preuter
|
||||
// improved by: Brett Zamir (http://brett-zamir.me)
|
||||
// improved by: Mirko Faber
|
||||
// input by: David
|
||||
// bugfixed by: Wagner B. Soares
|
||||
// bugfixed by: Artur Tchernychev
|
||||
// bugfixed by: Stephan Bösch-Plepelits (http://github.com/plepe)
|
||||
// note 1: Examples all have a fixed timestamp to prevent
|
||||
// note 1: tests to fail because of variable time(zones)
|
||||
// example 1: strtotime('+1 day', 1129633200)
|
||||
// returns 1: 1129719600
|
||||
// example 2: strtotime('+1 week 2 days 4 hours 2 seconds', 1129633200)
|
||||
// returns 2: 1130425202
|
||||
// example 3: strtotime('last month', 1129633200)
|
||||
// returns 3: 1127041200
|
||||
// example 4: strtotime('2009-05-04 08:30:00 GMT')
|
||||
// returns 4: 1241425800
|
||||
// example 5: strtotime('2009-05-04 08:30:00+00')
|
||||
// returns 5: 1241425800
|
||||
// example 6: strtotime('2009-05-04 08:30:00+02:00')
|
||||
// returns 6: 1241418600
|
||||
// example 7: strtotime('2009-05-04T08:30:00Z')
|
||||
// returns 7: 1241425800
|
||||
var parsed
|
||||
var match
|
||||
var today
|
||||
var year
|
||||
var date
|
||||
var days
|
||||
var ranges
|
||||
var len
|
||||
var times
|
||||
var regex
|
||||
var i
|
||||
var fail = false
|
||||
if (!text) {
|
||||
return fail
|
||||
}
|
||||
// Unecessary spaces
|
||||
text = text.replace(/^\s+|\s+$/g, '')
|
||||
.replace(/\s{2,}/g, ' ')
|
||||
.replace(/[\t\r\n]/g, '')
|
||||
.toLowerCase()
|
||||
// in contrast to php, js Date.parse function interprets:
|
||||
// dates given as yyyy-mm-dd as in timezone: UTC,
|
||||
// dates with "." or "-" as MDY instead of DMY
|
||||
// dates with two-digit years differently
|
||||
// etc...etc...
|
||||
// ...therefore we manually parse lots of common date formats
|
||||
var pattern = new RegExp([
|
||||
'^(\\d{1,4})',
|
||||
'([\\-\\.\\/:])',
|
||||
'(\\d{1,2})',
|
||||
'([\\-\\.\\/:])',
|
||||
'(\\d{1,4})',
|
||||
'(?:\\s(\\d{1,2}):(\\d{2})?:?(\\d{2})?)?',
|
||||
'(?:\\s([A-Z]+)?)?$'
|
||||
].join(''))
|
||||
match = text.match(pattern)
|
||||
if (match && match[2] === match[4]) {
|
||||
if (match[1] > 1901) {
|
||||
switch (match[2]) {
|
||||
case '-':
|
||||
// YYYY-M-D
|
||||
if (match[3] > 12 || match[5] > 31) {
|
||||
return fail
|
||||
}
|
||||
return new Date(match[1], parseInt(match[3], 10) - 1, match[5],
|
||||
match[6] || 0, match[7] || 0, match[8] || 0, match[9] || 0) / 1000
|
||||
case '.':
|
||||
// YYYY.M.D is not parsed by strtotime()
|
||||
return fail
|
||||
case '/':
|
||||
// YYYY/M/D
|
||||
if (match[3] > 12 || match[5] > 31) {
|
||||
return fail
|
||||
}
|
||||
return new Date(match[1], parseInt(match[3], 10) - 1, match[5],
|
||||
match[6] || 0, match[7] || 0, match[8] || 0, match[9] || 0) / 1000
|
||||
}
|
||||
} else if (match[5] > 1901) {
|
||||
switch (match[2]) {
|
||||
case '-':
|
||||
// D-M-YYYY
|
||||
if (match[3] > 12 || match[1] > 31) {
|
||||
return fail
|
||||
}
|
||||
return new Date(match[5], parseInt(match[3], 10) - 1, match[1],
|
||||
match[6] || 0, match[7] || 0, match[8] || 0, match[9] || 0) / 1000
|
||||
case '.':
|
||||
// D.M.YYYY
|
||||
if (match[3] > 12 || match[1] > 31) {
|
||||
return fail
|
||||
}
|
||||
return new Date(match[5], parseInt(match[3], 10) - 1, match[1],
|
||||
match[6] || 0, match[7] || 0, match[8] || 0, match[9] || 0) / 1000
|
||||
case '/':
|
||||
// M/D/YYYY
|
||||
if (match[1] > 12 || match[3] > 31) {
|
||||
return fail
|
||||
}
|
||||
return new Date(match[5], parseInt(match[1], 10) - 1, match[3],
|
||||
match[6] || 0, match[7] || 0, match[8] || 0, match[9] || 0) / 1000
|
||||
}
|
||||
} else {
|
||||
switch (match[2]) {
|
||||
case '-':
|
||||
// YY-M-D
|
||||
if (match[3] > 12 || match[5] > 31 || (match[1] < 70 && match[1] > 38)) {
|
||||
return fail
|
||||
}
|
||||
year = match[1] >= 0 && match[1] <= 38 ? +match[1] + 2000 : match[1]
|
||||
return new Date(year, parseInt(match[3], 10) - 1, match[5],
|
||||
match[6] || 0, match[7] || 0, match[8] || 0, match[9] || 0) / 1000
|
||||
case '.':
|
||||
// D.M.YY or H.MM.SS
|
||||
if (match[5] >= 70) {
|
||||
// D.M.YY
|
||||
if (match[3] > 12 || match[1] > 31) {
|
||||
return fail
|
||||
}
|
||||
return new Date(match[5], parseInt(match[3], 10) - 1, match[1],
|
||||
match[6] || 0, match[7] || 0, match[8] || 0, match[9] || 0) / 1000
|
||||
}
|
||||
if (match[5] < 60 && !match[6]) {
|
||||
// H.MM.SS
|
||||
if (match[1] > 23 || match[3] > 59) {
|
||||
return fail
|
||||
}
|
||||
today = new Date()
|
||||
return new Date(today.getFullYear(), today.getMonth(), today.getDate(),
|
||||
match[1] || 0, match[3] || 0, match[5] || 0, match[9] || 0) / 1000
|
||||
}
|
||||
// invalid format, cannot be parsed
|
||||
return fail
|
||||
case '/':
|
||||
// M/D/YY
|
||||
if (match[1] > 12 || match[3] > 31 || (match[5] < 70 && match[5] > 38)) {
|
||||
return fail
|
||||
}
|
||||
year = match[5] >= 0 && match[5] <= 38 ? +match[5] + 2000 : match[5]
|
||||
return new Date(year, parseInt(match[1], 10) - 1, match[3],
|
||||
match[6] || 0, match[7] || 0, match[8] || 0, match[9] || 0) / 1000
|
||||
case ':':
|
||||
// HH:MM:SS
|
||||
if (match[1] > 23 || match[3] > 59 || match[5] > 59) {
|
||||
return fail
|
||||
}
|
||||
today = new Date()
|
||||
return new Date(today.getFullYear(), today.getMonth(), today.getDate(),
|
||||
match[1] || 0, match[3] || 0, match[5] || 0) / 1000
|
||||
}
|
||||
}
|
||||
}
|
||||
// other formats and "now" should be parsed by Date.parse()
|
||||
if (text === 'now') {
|
||||
return now === null || isNaN(now)
|
||||
? new Date().getTime() / 1000 | 0
|
||||
: now | 0
|
||||
}
|
||||
if (!isNaN(parsed = Date.parse(text))) {
|
||||
return parsed / 1000 | 0
|
||||
}
|
||||
// Browsers !== Chrome have problems parsing ISO 8601 date strings, as they do
|
||||
// not accept lower case characters, space, or shortened time zones.
|
||||
// Therefore, fix these problems and try again.
|
||||
// Examples:
|
||||
// 2015-04-15 20:33:59+02
|
||||
// 2015-04-15 20:33:59z
|
||||
// 2015-04-15t20:33:59+02:00
|
||||
pattern = new RegExp([
|
||||
'^([0-9]{4}-[0-9]{2}-[0-9]{2})',
|
||||
'[ t]',
|
||||
'([0-9]{2}:[0-9]{2}:[0-9]{2}(\\.[0-9]+)?)',
|
||||
'([\\+-][0-9]{2}(:[0-9]{2})?|z)'
|
||||
].join(''))
|
||||
match = text.match(pattern)
|
||||
if (match) {
|
||||
// @todo: time zone information
|
||||
if (match[4] === 'z') {
|
||||
match[4] = 'Z'
|
||||
} else if (match[4].match(/^([+-][0-9]{2})$/)) {
|
||||
match[4] = match[4] + ':00'
|
||||
}
|
||||
if (!isNaN(parsed = Date.parse(match[1] + 'T' + match[2] + match[4]))) {
|
||||
return parsed / 1000 | 0
|
||||
}
|
||||
}
|
||||
date = now ? new Date(now * 1000) : new Date()
|
||||
days = {
|
||||
'sun': 0,
|
||||
'mon': 1,
|
||||
'tue': 2,
|
||||
'wed': 3,
|
||||
'thu': 4,
|
||||
'fri': 5,
|
||||
'sat': 6
|
||||
}
|
||||
ranges = {
|
||||
'yea': 'FullYear',
|
||||
'mon': 'Month',
|
||||
'day': 'Date',
|
||||
'hou': 'Hours',
|
||||
'min': 'Minutes',
|
||||
'sec': 'Seconds'
|
||||
}
|
||||
function lastNext (type, range, modifier) {
|
||||
var diff
|
||||
var day = days[range]
|
||||
if (typeof day !== 'undefined') {
|
||||
diff = day - date.getDay()
|
||||
if (diff === 0) {
|
||||
diff = 7 * modifier
|
||||
} else if (diff > 0 && type === 'last') {
|
||||
diff -= 7
|
||||
} else if (diff < 0 && type === 'next') {
|
||||
diff += 7
|
||||
}
|
||||
date.setDate(date.getDate() + diff)
|
||||
}
|
||||
}
|
||||
function process (val) {
|
||||
// @todo: Reconcile this with regex using \s, taking into account
|
||||
// browser issues with split and regexes
|
||||
var splt = val.split(' ')
|
||||
var type = splt[0]
|
||||
var range = splt[1].substring(0, 3)
|
||||
var typeIsNumber = /\d+/.test(type)
|
||||
var ago = splt[2] === 'ago'
|
||||
var num = (type === 'last' ? -1 : 1) * (ago ? -1 : 1)
|
||||
if (typeIsNumber) {
|
||||
num *= parseInt(type, 10)
|
||||
}
|
||||
if (ranges.hasOwnProperty(range) && !splt[1].match(/^mon(day|\.)?$/i)) {
|
||||
return date['set' + ranges[range]](date['get' + ranges[range]]() + num)
|
||||
}
|
||||
if (range === 'wee') {
|
||||
return date.setDate(date.getDate() + (num * 7))
|
||||
}
|
||||
if (type === 'next' || type === 'last') {
|
||||
lastNext(type, range, num)
|
||||
} else if (!typeIsNumber) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
times = '(years?|months?|weeks?|days?|hours?|minutes?|min|seconds?|sec' +
|
||||
'|sunday|sun\\.?|monday|mon\\.?|tuesday|tue\\.?|wednesday|wed\\.?' +
|
||||
'|thursday|thu\\.?|friday|fri\\.?|saturday|sat\\.?)'
|
||||
regex = '([+-]?\\d+\\s' + times + '|' + '(last|next)\\s' + times + ')(\\sago)?'
|
||||
match = text.match(new RegExp(regex, 'gi'))
|
||||
if (!match) {
|
||||
return fail
|
||||
}
|
||||
for (i = 0, len = match.length; i < len; i++) {
|
||||
if (!process(match[i])) {
|
||||
return fail
|
||||
}
|
||||
}
|
||||
return (date.getTime() / 1000)
|
||||
return new Intl.DateTimeFormat(navigator.languages, {
|
||||
hourCycle: 'h24',
|
||||
...format
|
||||
}).format(new Date(datetime));
|
||||
}
|
||||
|
||||
return {
|
||||
format: format,
|
||||
strtotime: strtotime
|
||||
}
|
||||
}(), true);
|
||||
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@
|
|||
|
||||
window.ls.container.set('form', function () {
|
||||
|
||||
function cast(value, to) {
|
||||
function cast(value, from, to,) {
|
||||
if (value && Array.isArray(value) && to !== 'array') {
|
||||
value = value.map(element => cast(element, to));
|
||||
value = value.map(element => cast(element, from, to));
|
||||
return value;
|
||||
}
|
||||
switch (to) {
|
||||
|
|
@ -29,7 +29,18 @@
|
|||
value = (value) ? JSON.parse(value) : [];
|
||||
break;
|
||||
case 'array':
|
||||
value = (value && value.constructor && value.constructor === Array) ? value : [value];
|
||||
if (value && value.constructor && value.constructor === Array) {
|
||||
break;
|
||||
}
|
||||
if (from === 'csv') {
|
||||
if (value.length === 0) {
|
||||
value = [];
|
||||
} else {
|
||||
value = value.split(',');
|
||||
}
|
||||
} else {
|
||||
value = [value];
|
||||
}
|
||||
break;
|
||||
case 'array-empty':
|
||||
value = [];
|
||||
|
|
@ -49,6 +60,7 @@
|
|||
let name = element.getAttribute('name');
|
||||
let type = element.getAttribute('type');
|
||||
let castTo = element.getAttribute('data-cast-to');
|
||||
let castFrom = element.getAttribute('data-cast-from');
|
||||
let ref = json;
|
||||
|
||||
if (name && 'FORM' !== element.tagName) {
|
||||
|
|
@ -121,7 +133,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
json[name] = cast(json[name], castTo); // Apply casting
|
||||
json[name] = cast(json[name], castFrom, castTo); // Apply casting
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -65,16 +65,12 @@
|
|||
const file = formData.get('file');
|
||||
const fileId = formData.get('fileId');
|
||||
let id = fileId === 'unique()' ? performance.now() : fileId;
|
||||
let read = formData.get('read');
|
||||
if(!file || !fileId) {
|
||||
return;
|
||||
}
|
||||
if(read) {
|
||||
read = JSON.parse(read);
|
||||
}
|
||||
let write = formData.get('write');
|
||||
if(write) {
|
||||
write = JSON.parse(write);
|
||||
let permissions = formData.get('permissions');
|
||||
if(permissions) {
|
||||
permissions = permissions.split(',');
|
||||
}
|
||||
|
||||
if(this.getFile(id)) {
|
||||
|
|
@ -103,8 +99,7 @@
|
|||
bucketId,
|
||||
fileId,
|
||||
file,
|
||||
read,
|
||||
write,
|
||||
permissions,
|
||||
(progress) => {
|
||||
this.updateFile(id, {
|
||||
id: progress.$id,
|
||||
|
|
|
|||
|
|
@ -13,7 +13,27 @@
|
|||
let showYAxis = element.getAttribute('data-show-y-axis') || false;
|
||||
let colors = (element.getAttribute('data-colors') || 'blue,green,orange,red').split(',');
|
||||
let themes = { 'blue': '#29b5d9', 'green': '#4eb55b', 'orange': '#fba233', 'red': '#dc3232', 'create': '#00b680', 'read': '#009cde', 'update': '#696fd7', 'delete': '#da5d95', };
|
||||
let range = { '24h': 'H:i', '7d': 'd F Y', '30d': 'd F Y', '90d': 'd F Y' }
|
||||
let range = {
|
||||
'24h': {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
},
|
||||
'7d': {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: '2-digit',
|
||||
},
|
||||
'30d': {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: '2-digit',
|
||||
},
|
||||
'90d': {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: '2-digit',
|
||||
}
|
||||
}
|
||||
let ticksCount = 5;
|
||||
|
||||
element.parentNode.insertBefore(wrapper, element.nextSibling);
|
||||
|
|
@ -97,10 +117,14 @@
|
|||
return;
|
||||
}
|
||||
|
||||
let dateFormat = (value.range && range[value.range]) ? range[value.range] : 'd F Y';
|
||||
let dateFormat = (value.range && range[value.range]) ? range[value.range] : {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: '2-digit',
|
||||
};
|
||||
|
||||
for (let x = 0; x < data.length; x++) {
|
||||
if(data[x].value > highest) {
|
||||
if (data[x].value > highest) {
|
||||
highest = data[x].value;
|
||||
}
|
||||
config.data.datasets[i].data[x] = data[x].value;
|
||||
|
|
@ -108,7 +132,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
if(highest == 0) {
|
||||
if (highest == 0) {
|
||||
config.options.scales.y.ticks.stepSize = 1;
|
||||
config.options.scales.y.max = ticksCount;
|
||||
} else {
|
||||
|
|
@ -117,8 +141,8 @@
|
|||
config.options.scales.y.ticks.stepSize = highest / ticksCount;
|
||||
config.options.scales.y.max = highest;
|
||||
}
|
||||
|
||||
if(chart) {
|
||||
|
||||
if (chart) {
|
||||
chart.destroy();
|
||||
}
|
||||
else {
|
||||
|
|
|
|||
|
|
@ -109,14 +109,11 @@
|
|||
input.addEventListener("change", function() {
|
||||
var message = alerts.add({ text: labelLoading, class: "" }, 0);
|
||||
var files = input.files;
|
||||
var read = JSON.parse(
|
||||
expression.parse(element.dataset["read"] || "[]")
|
||||
);
|
||||
var write = JSON.parse(
|
||||
expression.parse(element.dataset["write"] || "[]")
|
||||
);
|
||||
var permissions = JSON.parse(
|
||||
expression.parse(element.dataset["permissions"] || "[]")
|
||||
)
|
||||
|
||||
sdk.storage.createFile('default', 'unique()', files[0], read, write).then(
|
||||
sdk.storage.createFile('default', 'unique()', files[0], permissions).then(
|
||||
function(response) {
|
||||
onComplete(message);
|
||||
|
||||
|
|
|
|||
24
public/styles/comps/permissions-matrix.less
Normal file
24
public/styles/comps/permissions-matrix.less
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
.permissions-matrix {
|
||||
th:first-child, td:first-child {
|
||||
width: 100px;
|
||||
}
|
||||
th:not(:first-child):not(:last-child), td:not(:first-child):not(:last-child) {
|
||||
width: 50px;
|
||||
text-align: center;
|
||||
}
|
||||
th:last-child, td:last-child {
|
||||
width: 20px;
|
||||
}
|
||||
td {
|
||||
vertical-align: middle;
|
||||
}
|
||||
input, p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
p {
|
||||
margin-left: 15px;
|
||||
}
|
||||
i {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
|
@ -38,6 +38,7 @@ img[src=""] {
|
|||
@import "comps/preview-box";
|
||||
@import "comps/upload-box";
|
||||
@import "comps/pill";
|
||||
@import "comps/permissions-matrix";
|
||||
|
||||
html {
|
||||
padding: 0;
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@
|
|||
.icon-boolean:before { content: "\ea0c"; }
|
||||
.icon-briefcase:before { content: "\ea0d"; }
|
||||
.icon-building-filled:before { content: "\ea0e"; }
|
||||
.icon-datetime:before { content: "\ea0f"; }
|
||||
.icon-calendar:before { content: "\ea0f"; }
|
||||
.icon-cancel-circled:before { content: "\ea10"; }
|
||||
.icon-cancel:before { content: "\ea11"; }
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ use Appwrite\Auth\Hash\Scrypt;
|
|||
use Appwrite\Auth\Hash\Scryptmodified;
|
||||
use Appwrite\Auth\Hash\Sha;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Role;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
|
||||
class Auth
|
||||
|
|
@ -31,13 +33,13 @@ class Auth
|
|||
/**
|
||||
* User Roles.
|
||||
*/
|
||||
public const USER_ROLE_ALL = 'all';
|
||||
public const USER_ROLE_GUEST = 'guest';
|
||||
public const USER_ROLE_MEMBER = 'member';
|
||||
public const USER_ROLE_ANY = 'any';
|
||||
public const USER_ROLE_GUESTS = 'guests';
|
||||
public const USER_ROLE_USERS = 'users';
|
||||
public const USER_ROLE_ADMIN = 'admin';
|
||||
public const USER_ROLE_DEVELOPER = 'developer';
|
||||
public const USER_ROLE_OWNER = 'owner';
|
||||
public const USER_ROLE_APP = 'app';
|
||||
public const USER_ROLE_APPS = 'apps';
|
||||
public const USER_ROLE_SYSTEM = 'system';
|
||||
|
||||
/**
|
||||
|
|
@ -315,7 +317,7 @@ class Auth
|
|||
$token->isSet('expire') &&
|
||||
$token->getAttribute('type') == $type &&
|
||||
$token->getAttribute('secret') === self::hash($secret) &&
|
||||
$token->getAttribute('expire') >= \time()
|
||||
DateTime::formatTz($token->getAttribute('expire')) >= DateTime::formatTz(DateTime::now())
|
||||
) {
|
||||
return (string)$token->getId();
|
||||
}
|
||||
|
|
@ -334,7 +336,7 @@ class Auth
|
|||
$token->isSet('expire') &&
|
||||
$token->getAttribute('type') == Auth::TOKEN_TYPE_PHONE &&
|
||||
$token->getAttribute('secret') === $secret &&
|
||||
$token->getAttribute('expire') >= \time()
|
||||
DateTime::formatTz($token->getAttribute('expire')) >= DateTime::formatTz(DateTime::now())
|
||||
) {
|
||||
return (string) $token->getId();
|
||||
}
|
||||
|
|
@ -360,9 +362,9 @@ class Auth
|
|||
$session->isSet('expire') &&
|
||||
$session->isSet('provider') &&
|
||||
$session->getAttribute('secret') === self::hash($secret) &&
|
||||
$session->getAttribute('expire') >= \time()
|
||||
DateTime::formatTz($session->getAttribute('expire')) >= DateTime::formatTz(DateTime::now())
|
||||
) {
|
||||
return (string)$session->getId();
|
||||
return $session->getId();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -379,9 +381,9 @@ class Auth
|
|||
public static function isPrivilegedUser(array $roles): bool
|
||||
{
|
||||
if (
|
||||
in_array('role:' . self::USER_ROLE_OWNER, $roles) ||
|
||||
in_array('role:' . self::USER_ROLE_DEVELOPER, $roles) ||
|
||||
in_array('role:' . self::USER_ROLE_ADMIN, $roles)
|
||||
in_array(self::USER_ROLE_OWNER, $roles) ||
|
||||
in_array(self::USER_ROLE_DEVELOPER, $roles) ||
|
||||
in_array(self::USER_ROLE_ADMIN, $roles)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
|
@ -398,7 +400,7 @@ class Auth
|
|||
*/
|
||||
public static function isAppUser(array $roles): bool
|
||||
{
|
||||
if (in_array('role:' . self::USER_ROLE_APP, $roles)) {
|
||||
if (in_array(self::USER_ROLE_APPS, $roles)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -417,19 +419,19 @@ class Auth
|
|||
|
||||
if (!self::isPrivilegedUser(Authorization::getRoles()) && !self::isAppUser(Authorization::getRoles())) {
|
||||
if ($user->getId()) {
|
||||
$roles[] = 'user:' . $user->getId();
|
||||
$roles[] = 'role:' . Auth::USER_ROLE_MEMBER;
|
||||
$roles[] = Role::user($user->getId())->toString();
|
||||
$roles[] = Role::users()->toString();
|
||||
} else {
|
||||
return ['role:' . Auth::USER_ROLE_GUEST];
|
||||
return [Role::guests()->toString()];
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($user->getAttribute('memberships', []) as $node) {
|
||||
if (isset($node['teamId']) && isset($node['roles'])) {
|
||||
$roles[] = 'team:' . $node['teamId'];
|
||||
$roles[] = Role::team($node['teamId'])->toString();
|
||||
|
||||
foreach ($node['roles'] as $nodeRole) { // Set all team roles
|
||||
$roles[] = 'team:' . $node['teamId'] . '/' . $nodeRole;
|
||||
$roles[] = Role::team($node['teamId'], $nodeRole)->toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
200
src/Appwrite/Auth/OAuth2/Etsy.php
Normal file
200
src/Appwrite/Auth/OAuth2/Etsy.php
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Auth\OAuth2;
|
||||
|
||||
use Appwrite\Auth\OAuth2;
|
||||
|
||||
class Etsy extends OAuth2
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private string $endpoint = 'https://api.etsy.com/v3/public';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private string $version = '2022-07-14';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected array $user = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected array $tokens = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected array $scopes = [
|
||||
"email_r",
|
||||
"profile_r",
|
||||
];
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private string $pkce = '';
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
private function getPKCE(): string
|
||||
{
|
||||
if (empty($this->pkce)) {
|
||||
$this->pkce = \bin2hex(\random_bytes(rand(43, 128)));
|
||||
}
|
||||
|
||||
return $this->pkce;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return 'etsy';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getLoginURL(): string
|
||||
{
|
||||
return 'https://www.etsy.com/oauth/connect/oauth/authorize?' . \http_build_query([
|
||||
'client_id' => $this->appID,
|
||||
'redirect_uri' => $this->callback,
|
||||
'response_type' => 'code',
|
||||
'state' => \json_encode($this->state),
|
||||
'scope' => $this->scopes,
|
||||
'code_challenge' => $this->getPKCE(),
|
||||
'code_challenge_method' => 'S256',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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',
|
||||
$this->endpoint . '/oauth/token',
|
||||
$headers,
|
||||
\http_build_query([
|
||||
'grant_type' => 'authorization_code',
|
||||
'client_id' => $this->appID,
|
||||
'redirect_uri' => $this->callback,
|
||||
'code' => $code,
|
||||
'code_verifier' => $this->getPKCE(),
|
||||
])
|
||||
), 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',
|
||||
$this->endpoint . '/oauth/token',
|
||||
$headers,
|
||||
\http_build_query([
|
||||
'grant_type' => 'refresh_token',
|
||||
'client_id' => $this->appID,
|
||||
'refresh_token' => $refreshToken,
|
||||
])
|
||||
), true);
|
||||
|
||||
if (empty($this->tokens['refresh_token'])) {
|
||||
$this->tokens['refresh_token'] = $refreshToken;
|
||||
}
|
||||
|
||||
return $this->tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $accessToken
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUserID(string $accessToken): string
|
||||
{
|
||||
$components = explode('.', $accessToken);
|
||||
|
||||
return $components[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $accessToken
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUserEmail(string $accessToken): string
|
||||
{
|
||||
return $this->getUser($accessToken)['primary_email'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the OAuth email is verified
|
||||
*
|
||||
* OAuth is only allowed if account has been verified through Etsy, itself.
|
||||
*
|
||||
* @param string $accessToken
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isEmailVerified(string $accessToken): bool
|
||||
{
|
||||
$email = $this->getUserEmail($accessToken);
|
||||
|
||||
return !empty($email);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $accessToken
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUserName(string $accessToken): string
|
||||
{
|
||||
return $this->getUser($accessToken)['login_name'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $accessToken
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getUser(string $accessToken): array
|
||||
{
|
||||
if (!empty($this->user)) {
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
$headers = ['Authorization: Bearer ' . $accessToken];
|
||||
|
||||
$this->user = \json_decode($this->request(
|
||||
'GET',
|
||||
'https://api.etsy.com/v3/application/users/' . $this->getUserID($accessToken),
|
||||
), true);
|
||||
|
||||
return $this->user;
|
||||
}
|
||||
}
|
||||
|
|
@ -8,11 +8,11 @@ use Utopia\Database\Document;
|
|||
class Delete extends Event
|
||||
{
|
||||
protected string $type = '';
|
||||
protected ?int $timestamp = null;
|
||||
protected ?int $timestamp1d = null;
|
||||
protected ?int $timestamp30m = null;
|
||||
protected ?Document $document = null;
|
||||
protected ?string $resource = null;
|
||||
protected ?string $datetime = null;
|
||||
protected ?string $datetime1d = null;
|
||||
protected ?string $datetime30m = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
|
|
@ -43,41 +43,38 @@ class Delete extends Event
|
|||
}
|
||||
|
||||
/**
|
||||
* Set timestamp.
|
||||
* set Datetime.
|
||||
*
|
||||
* @param int $timestamp
|
||||
* @param string $datetime
|
||||
* @return self
|
||||
*/
|
||||
public function setTimestamp(int $timestamp): self
|
||||
public function setDatetime(string $datetime): self
|
||||
{
|
||||
$this->timestamp = $timestamp;
|
||||
|
||||
$this->datetime = $datetime;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set timestamp for 1 day interval.
|
||||
* Set datetime for 1 day interval.
|
||||
*
|
||||
* @param int $timestamp
|
||||
* @param string $datetime
|
||||
* @return self
|
||||
*/
|
||||
public function setTimestamp1d(int $timestamp): self
|
||||
public function setDatetime1d(string $datetime): self
|
||||
{
|
||||
$this->timestamp1d = $timestamp;
|
||||
|
||||
$this->datetime1d = $datetime;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets timestamp for 30m interval.
|
||||
* Sets datetime for 30m interval.
|
||||
*
|
||||
* @param int $timestamp
|
||||
* @param string $datetime
|
||||
* @return self
|
||||
*/
|
||||
public function setTimestamp30m(int $timestamp): self
|
||||
public function setDatetime30m(string $datetime): self
|
||||
{
|
||||
$this->timestamp30m = $timestamp;
|
||||
|
||||
$this->datetime30m = $datetime;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
|
@ -140,10 +137,10 @@ class Delete extends Event
|
|||
'project' => $this->project,
|
||||
'type' => $this->type,
|
||||
'document' => $this->document,
|
||||
'timestamp' => $this->timestamp,
|
||||
'timestamp1d' => $this->timestamp1d,
|
||||
'timestamp30m' => $this->timestamp30m,
|
||||
'resource' => $this->resource,
|
||||
'datetime' => $this->datetime,
|
||||
'datetime1d' => $this->datetime1d,
|
||||
'datetime30m' => $this->datetime30m,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,9 +2,12 @@
|
|||
|
||||
namespace Appwrite\Messaging\Adapter;
|
||||
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Appwrite\Messaging\Adapter;
|
||||
use Utopia\App;
|
||||
use Utopia\Database\ID;
|
||||
use Utopia\Database\Role;
|
||||
|
||||
class Realtime extends Adapter
|
||||
{
|
||||
|
|
@ -146,7 +149,7 @@ class Realtime extends Adapter
|
|||
'data' => [
|
||||
'events' => $events,
|
||||
'channels' => $channels,
|
||||
'timestamp' => time(),
|
||||
'timestamp' => DateTime::now(),
|
||||
'payload' => $payload
|
||||
]
|
||||
]));
|
||||
|
|
@ -186,7 +189,7 @@ class Realtime extends Adapter
|
|||
*/
|
||||
if (
|
||||
\array_key_exists($channel, $this->subscriptions[$event['project']][$role])
|
||||
&& (\in_array($role, $event['roles']) || \in_array('role:all', $event['roles']))
|
||||
&& (\in_array($role, $event['roles']) || \in_array(Role::any()->toString(), $event['roles']))
|
||||
) {
|
||||
/**
|
||||
* Saving all connections that are allowed to receive this event.
|
||||
|
|
@ -255,27 +258,25 @@ class Realtime extends Adapter
|
|||
case 'users':
|
||||
$channels[] = 'account';
|
||||
$channels[] = 'account.' . $parts[1];
|
||||
$roles = ['user:' . $parts[1]];
|
||||
|
||||
$roles = [Role::user(ID::custom($parts[1]))->toString()];
|
||||
break;
|
||||
case 'teams':
|
||||
if ($parts[2] === 'memberships') {
|
||||
$permissionsChanged = $parts[4] ?? false;
|
||||
$channels[] = 'memberships';
|
||||
$channels[] = 'memberships.' . $parts[3];
|
||||
$roles = ['team:' . $parts[1]];
|
||||
} else {
|
||||
$permissionsChanged = $parts[2] === 'create';
|
||||
$channels[] = 'teams';
|
||||
$channels[] = 'teams.' . $parts[1];
|
||||
$roles = ['team:' . $parts[1]];
|
||||
}
|
||||
$roles = [Role::team(ID::custom($parts[1]))->toString()];
|
||||
break;
|
||||
case 'databases':
|
||||
if (in_array($parts[4] ?? [], ['attributes', 'indexes'])) {
|
||||
$channels[] = 'console';
|
||||
$projectId = 'console';
|
||||
$roles = ['team:' . $project->getAttribute('teamId')];
|
||||
$roles = [Role::team($project->getAttribute('teamId'))->toString()];
|
||||
} elseif (($parts[4] ?? '') === 'documents') {
|
||||
if ($database->isEmpty()) {
|
||||
throw new \Exception('Database needs to be passed to Realtime for Document events in the Database.');
|
||||
|
|
@ -288,18 +289,23 @@ class Realtime extends Adapter
|
|||
$channels[] = 'databases.' . $database->getId() . '.collections.' . $payload->getCollection() . '.documents';
|
||||
$channels[] = 'databases.' . $database->getId() . '.collections.' . $payload->getCollection() . '.documents.' . $payload->getId();
|
||||
|
||||
$roles = ($collection->getAttribute('permission') === 'collection') ? $collection->getRead() : $payload->getRead();
|
||||
$roles = $collection->getAttribute('documentSecurity', false)
|
||||
? \array_merge($collection->getRead(), $payload->getRead())
|
||||
: $collection->getRead();
|
||||
}
|
||||
break;
|
||||
case 'buckets':
|
||||
if ($parts[2] === 'files') {
|
||||
if ($bucket->isEmpty()) {
|
||||
throw new \Exception('Bucket needs to be pased to Realtime for File events in the Storage.');
|
||||
throw new \Exception('Bucket needs to be passed to Realtime for File events in the Storage.');
|
||||
}
|
||||
$channels[] = 'files';
|
||||
$channels[] = 'buckets.' . $payload->getAttribute('bucketId') . '.files';
|
||||
$channels[] = 'buckets.' . $payload->getAttribute('bucketId') . '.files.' . $payload->getId();
|
||||
$roles = ($bucket->getAttribute('permission') === 'bucket') ? $bucket->getRead() : $payload->getRead();
|
||||
|
||||
$roles = $bucket->getAttribute('fileSecurity', false)
|
||||
? \array_merge($bucket->getRead(), $payload->getRead())
|
||||
: $bucket->getRead();
|
||||
}
|
||||
|
||||
break;
|
||||
|
|
@ -315,7 +321,8 @@ class Realtime extends Adapter
|
|||
}
|
||||
} elseif ($parts[2] === 'deployments') {
|
||||
$channels[] = 'console';
|
||||
$roles = ['team:' . $project->getAttribute('teamId')];
|
||||
|
||||
$roles = [Role::team($project->getAttribute('teamId'))->toString()];
|
||||
}
|
||||
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -5,10 +5,12 @@ namespace Appwrite\Migration;
|
|||
use Swoole\Runtime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Config\Config;
|
||||
use Exception;
|
||||
use Utopia\App;
|
||||
use Utopia\Database\ID;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
|
||||
abstract class Migration
|
||||
|
|
@ -62,15 +64,15 @@ abstract class Migration
|
|||
Authorization::setDefaultStatus(false);
|
||||
$this->collections = array_merge([
|
||||
'_metadata' => [
|
||||
'$id' => '_metadata',
|
||||
'$id' => ID::custom('_metadata'),
|
||||
'$collection' => Database::METADATA
|
||||
],
|
||||
'audit' => [
|
||||
'$id' => 'audit',
|
||||
'$id' => ID::custom('audit'),
|
||||
'$collection' => Database::METADATA
|
||||
],
|
||||
'abuse' => [
|
||||
'$id' => 'abuse',
|
||||
'$id' => ID::custom('abuse'),
|
||||
'$collection' => Database::METADATA
|
||||
]
|
||||
], Config::getParam('collections', []));
|
||||
|
|
@ -116,7 +118,11 @@ abstract class Migration
|
|||
Console::log('Migrating Collection ' . $collection['$id'] . ':');
|
||||
|
||||
do {
|
||||
$documents = $this->projectDB->find($collection['$id'], limit: $this->limit, cursor: $nextDocument);
|
||||
$queries = [Query::limit($this->limit)];
|
||||
if ($nextDocument !== null) {
|
||||
$queries[] = Query::cursorAfter($nextDocument);
|
||||
}
|
||||
$documents = $this->projectDB->find($collection['$id'], $queries);
|
||||
$count = count($documents);
|
||||
$sum += $count;
|
||||
|
||||
|
|
|
|||
|
|
@ -156,10 +156,10 @@ class V12 extends Migration
|
|||
*/
|
||||
$this->createCollection('buckets');
|
||||
|
||||
if (!$this->projectDB->findOne('buckets', [new Query('$id', Query::TYPE_EQUAL, ['default'])])) {
|
||||
if (!$this->projectDB->findOne('buckets', [Query::equal('$id', ['default'])])) {
|
||||
$this->projectDB->createDocument('buckets', new Document([
|
||||
'$id' => 'default',
|
||||
'$collection' => 'buckets',
|
||||
'$id' => ID::custom('default'),
|
||||
'$collection' => ID::custom('buckets'),
|
||||
'dateCreated' => \time(),
|
||||
'dateUpdated' => \time(),
|
||||
'name' => 'Default',
|
||||
|
|
@ -180,7 +180,11 @@ class V12 extends Migration
|
|||
*/
|
||||
$nextDocument = null;
|
||||
do {
|
||||
$documents = $this->projectDB->find('files', limit: $this->limit, cursor: $nextDocument);
|
||||
$queries = [Query::limit($this->limit)];
|
||||
if ($nextDocument !== null) {
|
||||
$queries[] = Query::cursorAfter($nextDocument);
|
||||
}
|
||||
$documents = $this->projectDB->find('files', $queries);
|
||||
$count = count($documents);
|
||||
\Co\run(function (array $documents) {
|
||||
foreach ($documents as $document) {
|
||||
|
|
@ -344,7 +348,11 @@ class V12 extends Migration
|
|||
$nextCollection = null;
|
||||
|
||||
do {
|
||||
$documents = $this->projectDB->find('collections', limit: $this->limit, cursor: $nextCollection);
|
||||
$queries = [Query::limit($this->limit)];
|
||||
if ($nextCollection !== null) {
|
||||
$queries[] = Query::cursorAfter($nextCollection);
|
||||
}
|
||||
$documents = $this->projectDB->find('collections', $queries);
|
||||
$count = count($documents);
|
||||
|
||||
\Co\run(function (array $documents) {
|
||||
|
|
@ -387,7 +395,11 @@ class V12 extends Migration
|
|||
$nextDocument = null;
|
||||
|
||||
do {
|
||||
$documents = $this->projectDB->find('collection_' . $internalId, limit: $this->limit, cursor: $nextDocument);
|
||||
$queries = [Query::limit($this->limit)];
|
||||
if ($nextDocument !== null) {
|
||||
$queries[] = Query::cursorAfter($nextDocument);
|
||||
}
|
||||
$documents = $this->projectDB->find('collection_' . $internalId, $queries);
|
||||
$count = count($documents);
|
||||
|
||||
foreach ($documents as $document) {
|
||||
|
|
@ -462,7 +474,11 @@ class V12 extends Migration
|
|||
$nextDocument = null;
|
||||
|
||||
do {
|
||||
$documents = $this->projectDB->find($id, limit: $this->limit, cursor: $nextDocument);
|
||||
$queries = [Query::limit($this->limit)];
|
||||
if ($nextDocument !== null) {
|
||||
$queries[] = Query::cursorAfter($nextDocument);
|
||||
}
|
||||
$documents = $this->projectDB->find($id, $queries);
|
||||
$count = count($documents);
|
||||
|
||||
\Co\run(function (array $documents) {
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ class V14 extends Migration
|
|||
|
||||
try {
|
||||
$this->projectDB->createDocument('databases', new Document([
|
||||
'$id' => 'default',
|
||||
'$id' => ID::custom('default'),
|
||||
'name' => 'Default',
|
||||
'search' => 'default Default'
|
||||
]));
|
||||
|
|
@ -87,7 +87,11 @@ class V14 extends Migration
|
|||
{
|
||||
$nextFile = null;
|
||||
do {
|
||||
$documents = $this->projectDB->find("bucket_{$bucket->getInternalId()}", limit: $this->limit, cursor: $nextFile);
|
||||
$queries = [Query::limit($this->limit)];
|
||||
if ($nextFile !== null) {
|
||||
$queries[] = Query::cursorAfter($nextFile);
|
||||
}
|
||||
$documents = $this->projectDB->find("bucket_{$bucket->getInternalId()}", $queries);
|
||||
$count = count($documents);
|
||||
|
||||
foreach ($documents as $document) {
|
||||
|
|
@ -164,7 +168,11 @@ class V14 extends Migration
|
|||
$nextCollection = null;
|
||||
|
||||
do {
|
||||
$documents = $this->projectDB->find('database_1', limit: $this->limit, cursor: $nextCollection);
|
||||
$queries = [Query::limit($this->limit)];
|
||||
if ($nextCollection !== null) {
|
||||
$queries[] = Query::cursorAfter($nextCollection);
|
||||
}
|
||||
$documents = $this->projectDB->find('database_1', $queries);
|
||||
$count = count($documents);
|
||||
|
||||
\Co\run(function (array $documents) {
|
||||
|
|
@ -235,10 +243,15 @@ class V14 extends Migration
|
|||
* Offset pagination instead of cursor, since documents are re-created!
|
||||
*/
|
||||
$offset = 0;
|
||||
$attributesCount = $this->projectDB->count($type, queries: [new Query('collectionId', Query::TYPE_EQUAL, [$collection->getId()])]);
|
||||
$attributesCount = $this->projectDB->count($type, queries: [Query::equal('collectionId', [$collection->getId()])]);
|
||||
|
||||
do {
|
||||
$documents = $this->projectDB->find($type, limit: $this->limit, offset: $offset, queries: [new Query('collectionId', Query::TYPE_EQUAL, [$collection->getId()])]);
|
||||
$queries = [
|
||||
Query::limit($this->limit),
|
||||
Query::offset($offset),
|
||||
Query::equal('collectionId', [$collection->getId()]),
|
||||
];
|
||||
$documents = $this->projectDB->find($type, $queries);
|
||||
$offset += $this->limit;
|
||||
|
||||
foreach ($documents as $document) {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ namespace Appwrite\Specification\Format;
|
|||
use Appwrite\Specification\Format;
|
||||
use Appwrite\Template\Template;
|
||||
use Appwrite\Utopia\Response\Model;
|
||||
use Utopia\Database\Permission;
|
||||
use Utopia\Database\Role;
|
||||
use Utopia\Validator;
|
||||
|
||||
class OpenAPI3 extends Format
|
||||
|
|
@ -299,6 +301,11 @@ class OpenAPI3 extends Format
|
|||
$node['schema']['type'] = $validator->getType();
|
||||
$node['schema']['x-example'] = '[' . \strtoupper(Template::fromCamelCaseToSnake($node['name'])) . ']';
|
||||
break;
|
||||
case 'Utopia\Database\Validator\DatetimeValidator':
|
||||
$node['schema']['type'] = $validator->getType();
|
||||
$node['schema']['format'] = 'datetime';
|
||||
$node['schema']['x-example'] = Model::TYPE_DATETIME_EXAMPLE;
|
||||
break;
|
||||
case 'Appwrite\Network\Validator\Email':
|
||||
$node['schema']['type'] = $validator->getType();
|
||||
$node['schema']['format'] = 'email';
|
||||
|
|
@ -333,6 +340,14 @@ class OpenAPI3 extends Format
|
|||
$node['schema']['items'] = [
|
||||
'type' => 'string',
|
||||
];
|
||||
$node['schema']['x-example'] = '["' . Permission::read(Role::any()) . '"]';
|
||||
break;
|
||||
case 'Utopia\Database\Validator\Roles':
|
||||
$node['schema']['type'] = $validator->getType();
|
||||
$node['schema']['items'] = [
|
||||
'type' => 'string',
|
||||
];
|
||||
$node['schema']['x-example'] = '["' . Role::any()->toString() . '"]';
|
||||
break;
|
||||
case 'Appwrite\Auth\Validator\Password':
|
||||
$node['schema']['type'] = $validator->getType();
|
||||
|
|
@ -464,6 +479,7 @@ class OpenAPI3 extends Format
|
|||
|
||||
switch ($rule['type']) {
|
||||
case 'string':
|
||||
case 'datetime':
|
||||
$type = 'string';
|
||||
break;
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ namespace Appwrite\Specification\Format;
|
|||
use Appwrite\Specification\Format;
|
||||
use Appwrite\Template\Template;
|
||||
use Appwrite\Utopia\Response\Model;
|
||||
use Utopia\Database\Permission;
|
||||
use Utopia\Database\Role;
|
||||
use Utopia\Validator;
|
||||
|
||||
class Swagger2 extends Format
|
||||
|
|
@ -295,6 +297,11 @@ class Swagger2 extends Format
|
|||
$node['type'] = $validator->getType();
|
||||
$node['x-example'] = '[' . \strtoupper(Template::fromCamelCaseToSnake($node['name'])) . ']';
|
||||
break;
|
||||
case 'Utopia\Database\Validator\DatetimeValidator':
|
||||
$node['type'] = $validator->getType();
|
||||
$node['format'] = 'datetime';
|
||||
$node['x-example'] = Model::TYPE_DATETIME_EXAMPLE;
|
||||
break;
|
||||
case 'Appwrite\Network\Validator\Email':
|
||||
$node['type'] = $validator->getType();
|
||||
$node['format'] = 'email';
|
||||
|
|
@ -329,7 +336,15 @@ class Swagger2 extends Format
|
|||
$node['items'] = [
|
||||
'type' => 'string',
|
||||
];
|
||||
$node['x-example'] = '["role:all"]';
|
||||
$node['x-example'] = '["' . Permission::read(Role::any()) . '"]';
|
||||
break;
|
||||
case 'Utopia\Database\Validator\Roles':
|
||||
$node['type'] = $validator->getType();
|
||||
$node['collectionFormat'] = 'multi';
|
||||
$node['items'] = [
|
||||
'type' => 'string',
|
||||
];
|
||||
$node['x-example'] = '["' . Role::any()->toString() . '"]';
|
||||
break;
|
||||
case 'Appwrite\Auth\Validator\Password':
|
||||
$node['type'] = $validator->getType();
|
||||
|
|
@ -463,6 +478,7 @@ class Swagger2 extends Format
|
|||
|
||||
switch ($rule['type']) {
|
||||
case 'string':
|
||||
case 'datetime':
|
||||
$type = 'string';
|
||||
break;
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Appwrite\Usage\Calculators;
|
||||
|
||||
use DateTime;
|
||||
use Utopia\Database\Database as UtopiaDatabase;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Query;
|
||||
|
|
@ -180,28 +181,29 @@ class Aggregator extends Database
|
|||
|
||||
protected function aggregateDailyMetric(string $projectId, string $metric): void
|
||||
{
|
||||
$beginOfDay = strtotime("today");
|
||||
$endOfDay = strtotime("tomorrow", $beginOfDay) - 1;
|
||||
$beginOfDay = DateTime::createFromFormat('Y-m-d\TH:i:s.v', \date('Y-m-d\T00:00:00.000'))->format(DateTime::RFC3339);
|
||||
$endOfDay = DateTime::createFromFormat('Y-m-d\TH:i:s.v', \date('Y-m-d\T23:59:59.999'))->format(DateTime::RFC3339);
|
||||
|
||||
$this->database->setNamespace('_' . $projectId);
|
||||
$value = (int) $this->database->sum('stats', 'value', [
|
||||
new Query('metric', Query::TYPE_EQUAL, [$metric]),
|
||||
new Query('period', Query::TYPE_EQUAL, ['30m']),
|
||||
new Query('time', Query::TYPE_GREATEREQUAL, [$beginOfDay]),
|
||||
new Query('time', Query::TYPE_LESSEREQUAL, [$endOfDay]),
|
||||
Query::equal('metric', [$metric]),
|
||||
Query::equal('period', ['30m']),
|
||||
Query::greaterThanEqual('time', $beginOfDay),
|
||||
Query::lessThanEqual('time', $endOfDay),
|
||||
]);
|
||||
$this->createOrUpdateMetric($projectId, $metric, '1d', $beginOfDay, $value);
|
||||
}
|
||||
|
||||
protected function aggregateMonthlyMetric(string $projectId, string $metric): void
|
||||
{
|
||||
$beginOfMonth = strtotime("first day of the month");
|
||||
$endOfMonth = strtotime("last day of the month");
|
||||
$beginOfMonth = DateTime::createFromFormat('Y-m-d\TH:i:s.v', \date('Y-m-01\T00:00:00.000'))->format(DateTime::RFC3339);
|
||||
$endOfMonth = DateTime::createFromFormat('Y-m-d\TH:i:s.v', \date('Y-m-t\T23:59:59.999'))->format(DateTime::RFC3339);
|
||||
$this->database->setNamespace('_' . $projectId);
|
||||
$value = (int) $this->database->sum('stats', 'value', [
|
||||
new Query('metric', Query::TYPE_EQUAL, [$metric]),
|
||||
new Query('period', Query::TYPE_EQUAL, ['1d']),
|
||||
new Query('time', Query::TYPE_GREATEREQUAL, [$beginOfMonth]),
|
||||
new Query('time', Query::TYPE_LESSEREQUAL, [$endOfMonth]),
|
||||
Query::equal('metric', [$metric]),
|
||||
Query::equal('period', ['1d']),
|
||||
Query::greaterThanEqual('time', $beginOfMonth),
|
||||
Query::lessThanEqual('time', $endOfMonth),
|
||||
]);
|
||||
$this->createOrUpdateMetric($projectId, $metric, '1mo', $beginOfMonth, $value);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,9 +2,13 @@
|
|||
|
||||
namespace Appwrite\Usage\Calculators;
|
||||
|
||||
use Exception;
|
||||
use Appwrite\Usage\Calculator;
|
||||
use DateTime;
|
||||
use Utopia\Database\Database as UtopiaDatabase;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Authorization;
|
||||
use Utopia\Database\Exception\Structure;
|
||||
use Utopia\Database\Query;
|
||||
|
||||
class Database extends Calculator
|
||||
|
|
@ -28,43 +32,59 @@ class Database extends Calculator
|
|||
|
||||
/**
|
||||
* Create Per Period Metric
|
||||
*
|
||||
* Create given metric for each defined period
|
||||
*
|
||||
* @param string $projectId
|
||||
* @param string $metric
|
||||
* @param int $value
|
||||
*
|
||||
* @param bool $monthly
|
||||
* @return void
|
||||
* @throws Authorization
|
||||
* @throws Structure
|
||||
*/
|
||||
protected function createPerPeriodMetric(string $projectId, string $metric, int $value, bool $monthly = false): void
|
||||
{
|
||||
foreach ($this->periods as $options) {
|
||||
$period = $options['key'];
|
||||
$time = (int) (floor(time() / $options['multiplier']) * $options['multiplier']);
|
||||
$date = new \DateTime();
|
||||
if ($period === '30m') {
|
||||
$minutes = $date->format('i') >= '30' ? "30" : "00";
|
||||
$time = $date->format('Y-m-d H:' . $minutes . ':00');
|
||||
} elseif ($period === '1d') {
|
||||
$time = $date->format('Y-m-d 00:00:00');
|
||||
} else {
|
||||
throw new Exception("Period type not found", 500);
|
||||
}
|
||||
$this->createOrUpdateMetric($projectId, $metric, $period, $time, $value);
|
||||
}
|
||||
|
||||
// Required for billing
|
||||
if ($monthly) {
|
||||
$time = strtotime("first day of the month");
|
||||
$time = DateTime::createFromFormat('Y-m-d\TH:i:s.v', \date('Y-m-01\T00:00:00.000'))->format(DateTime::RFC3339);
|
||||
$this->createOrUpdateMetric($projectId, $metric, '1mo', $time, $value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create or Update Mertic
|
||||
* Create or Update Metric
|
||||
*
|
||||
* Create or update each metric in the stats collection for the given project
|
||||
*
|
||||
* @param string $projectId
|
||||
* @param string $metric
|
||||
* @param string $period
|
||||
* @param string $time
|
||||
* @param int $value
|
||||
*
|
||||
* @return void
|
||||
* @throws Authorization
|
||||
* @throws Structure
|
||||
*/
|
||||
protected function createOrUpdateMetric(string $projectId, string $metric, string $period, int $time, int $value): void
|
||||
protected function createOrUpdateMetric(string $projectId, string $metric, string $period, string $time, int $value): void
|
||||
{
|
||||
$id = \md5("{$time}_{$period}_{$metric}");
|
||||
$this->database->setNamespace('_' . $projectId);
|
||||
$id = \md5("{$time}_{$period}_{$metric}");
|
||||
$this->database->setNamespace('_' . $projectId);
|
||||
|
||||
try {
|
||||
$document = $this->database->getDocument('stats', $id);
|
||||
|
|
@ -95,6 +115,7 @@ class Database extends Calculator
|
|||
|
||||
/**
|
||||
* Foreach Document
|
||||
*
|
||||
* Call provided callback for each document in the collection
|
||||
*
|
||||
* @param string $projectId
|
||||
|
|
@ -103,6 +124,7 @@ class Database extends Calculator
|
|||
* @param callable $callback
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function foreachDocument(string $projectId, string $collection, array $queries, callable $callback): void
|
||||
{
|
||||
|
|
@ -114,7 +136,11 @@ class Database extends Calculator
|
|||
|
||||
while ($sum === $limit) {
|
||||
try {
|
||||
$results = $this->database->find($collection, $queries, $limit, cursor:$latestDocument);
|
||||
$paginationQueries = [Query::limit($limit)];
|
||||
if ($latestDocument !== null) {
|
||||
$paginationQueries[] = Query::cursorAfter($latestDocument);
|
||||
}
|
||||
$results = $this->database->find($collection, \array_merge($paginationQueries, $queries));
|
||||
} catch (\Exception $e) {
|
||||
if (is_callable($this->errorHandler)) {
|
||||
call_user_func($this->errorHandler, $e, "fetch_documents_project_{$projectId}_collection_{$collection}");
|
||||
|
|
@ -140,14 +166,16 @@ class Database extends Calculator
|
|||
|
||||
/**
|
||||
* Sum
|
||||
* Calculate sum of a attribute of documents in collection
|
||||
*
|
||||
* Calculate sum of an attribute of documents in collection
|
||||
*
|
||||
* @param string $projectId
|
||||
* @param string $collection
|
||||
* @param string $attribute
|
||||
* @param string $metric
|
||||
*
|
||||
* @param string|null $metric
|
||||
* @param int $multiplier
|
||||
* @return int
|
||||
* @throws Exception
|
||||
*/
|
||||
private function sum(string $projectId, string $collection, string $attribute, string $metric = null, int $multiplier = 1): int
|
||||
{
|
||||
|
|
@ -161,7 +189,7 @@ class Database extends Calculator
|
|||
$this->createPerPeriodMetric($projectId, $metric, $sum);
|
||||
}
|
||||
return $sum;
|
||||
} catch (\Exception $e) {
|
||||
} catch (Exception $e) {
|
||||
if (is_callable($this->errorHandler)) {
|
||||
call_user_func($this->errorHandler, $e, "fetch_sum_project_{$projectId}_collection_{$collection}");
|
||||
} else {
|
||||
|
|
@ -173,15 +201,17 @@ class Database extends Calculator
|
|||
|
||||
/**
|
||||
* Count
|
||||
*
|
||||
* Count number of documents in collection
|
||||
*
|
||||
* @param string $projectId
|
||||
* @param string $collection
|
||||
* @param string? $metric
|
||||
* @param ?string $metric
|
||||
*
|
||||
* @return int
|
||||
* @throws Exception
|
||||
*/
|
||||
private function count(string $projectId, string $collection, string $metric = null): int
|
||||
private function count(string $projectId, string $collection, ?string $metric = null): int
|
||||
{
|
||||
$this->database->setNamespace('_' . $projectId);
|
||||
|
||||
|
|
@ -191,7 +221,7 @@ class Database extends Calculator
|
|||
$this->createPerPeriodMetric($projectId, (string) $metric, $count);
|
||||
}
|
||||
return $count;
|
||||
} catch (\Exception $e) {
|
||||
} catch (Exception $e) {
|
||||
if (is_callable($this->errorHandler)) {
|
||||
call_user_func($this->errorHandler, $e, "fetch_count_project_{$projectId}_collection_{$collection}");
|
||||
} else {
|
||||
|
|
@ -203,11 +233,13 @@ class Database extends Calculator
|
|||
|
||||
/**
|
||||
* Deployments Total
|
||||
*
|
||||
* Total sum of storage used by deployments
|
||||
*
|
||||
* @param string $projectId
|
||||
*
|
||||
* @return int
|
||||
* @throws Exception
|
||||
*/
|
||||
private function deploymentsTotal(string $projectId): int
|
||||
{
|
||||
|
|
@ -216,11 +248,13 @@ class Database extends Calculator
|
|||
|
||||
/**
|
||||
* Users Stats
|
||||
*
|
||||
* Metric: users.count
|
||||
*
|
||||
* @param string $projectId
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
private function usersStats(string $projectId): void
|
||||
{
|
||||
|
|
@ -229,12 +263,15 @@ class Database extends Calculator
|
|||
|
||||
/**
|
||||
* Storage Stats
|
||||
*
|
||||
* Metrics: buckets.$all.count.total, files.$all.count.total, files.bucketId,count.total,
|
||||
* files.$all.storage.size, files.bucketId.storage.size, project.$all.storage.size
|
||||
*
|
||||
* @param string $projectId
|
||||
*
|
||||
* @return void
|
||||
* @throws Authorization
|
||||
* @throws Structure
|
||||
*/
|
||||
private function storageStats(string $projectId): void
|
||||
{
|
||||
|
|
@ -263,6 +300,7 @@ class Database extends Calculator
|
|||
|
||||
/**
|
||||
* Database Stats
|
||||
*
|
||||
* Collect all database stats
|
||||
* Metrics: databases.$all.count.total, collections.$all.count.total, collections.databaseId.count.total,
|
||||
* documents.$all.count.all, documents.databaseId.count.total, documents.databaseId/collectionId.count.total
|
||||
|
|
@ -270,6 +308,8 @@ class Database extends Calculator
|
|||
* @param string $projectId
|
||||
*
|
||||
* @return void
|
||||
* @throws Authorization
|
||||
* @throws Structure
|
||||
*/
|
||||
private function databaseStats(string $projectId): void
|
||||
{
|
||||
|
|
@ -301,9 +341,11 @@ class Database extends Calculator
|
|||
|
||||
/**
|
||||
* Collect Stats
|
||||
*
|
||||
* Collect all database related stats
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function collect(): void
|
||||
{
|
||||
|
|
|
|||
|
|
@ -298,7 +298,7 @@ class TimeSeries extends Calculator
|
|||
*
|
||||
* @return void
|
||||
*/
|
||||
private function createOrUpdateMetric(string $projectId, int $time, string $period, string $metric, int $value, int $type): void
|
||||
private function createOrUpdateMetric(string $projectId, string $time, string $period, string $metric, int $value, int $type): void
|
||||
{
|
||||
$id = \md5("{$time}_{$period}_{$metric}");
|
||||
$this->database->setNamespace('_console');
|
||||
|
|
@ -347,9 +347,9 @@ class TimeSeries extends Calculator
|
|||
{
|
||||
$start = DateTime::createFromFormat('U', \strtotime($period['startTime']))->format(DateTime::RFC3339);
|
||||
if (!empty($this->latestTime[$metric][$period['key']])) {
|
||||
$start = DateTime::createFromFormat('U', $this->latestTime[$metric][$period['key']])->format(DateTime::RFC3339);
|
||||
$start = $this->latestTime[$metric][$period['key']];
|
||||
}
|
||||
$end = DateTime::createFromFormat('U', \strtotime('now'))->format(DateTime::RFC3339);
|
||||
$end = (new DateTime())->format(DateTime::RFC3339);
|
||||
|
||||
$table = $options['table']; //Which influxdb table to query for this metric
|
||||
$groupBy = empty($options['groupBy']) ? '' : ', ' . implode(', ', array_map(fn($groupBy) => '"' . $groupBy . '" ', $options['groupBy'])); //Some sub level metrics may be grouped by other tags like collectionId, bucketId, etc
|
||||
|
|
@ -387,12 +387,11 @@ class TimeSeries extends Calculator
|
|||
}
|
||||
}
|
||||
|
||||
$time = \strtotime($point['time']);
|
||||
$value = (!empty($point['value'])) ? $point['value'] : 0;
|
||||
|
||||
$this->createOrUpdateMetric(
|
||||
$projectId,
|
||||
$time,
|
||||
$point['time'],
|
||||
$period['key'],
|
||||
$metricUpdated,
|
||||
$value,
|
||||
|
|
|
|||
153
src/Appwrite/Utopia/Database/Validator/IndexedQueries.php
Normal file
153
src/Appwrite/Utopia/Database/Validator/IndexedQueries.php
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Database\Validator;
|
||||
|
||||
use Appwrite\Utopia\Database\Validator\Query\Base;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Query;
|
||||
|
||||
class IndexedQueries extends Queries
|
||||
{
|
||||
/**
|
||||
* @var Document[]
|
||||
*/
|
||||
protected $attributes = [];
|
||||
|
||||
/**
|
||||
* @var Document[]
|
||||
*/
|
||||
protected $indexes = [];
|
||||
|
||||
/**
|
||||
* Expression constructor
|
||||
*
|
||||
* This Queries Validator filters indexes for only available indexes
|
||||
*
|
||||
* @param Document[] $attributes
|
||||
* @param Document[] $indexes
|
||||
* @param Base ...$validators
|
||||
* @param bool $strict
|
||||
*/
|
||||
public function __construct($attributes = [], $indexes = [], Base ...$validators)
|
||||
{
|
||||
$this->attributes = $attributes;
|
||||
|
||||
$this->indexes[] = new Document([
|
||||
'type' => Database::INDEX_UNIQUE,
|
||||
'attributes' => ['$id']
|
||||
]);
|
||||
|
||||
$this->indexes[] = new Document([
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['$createdAt']
|
||||
]);
|
||||
|
||||
$this->indexes[] = new Document([
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['$updatedAt']
|
||||
]);
|
||||
|
||||
foreach ($indexes ?? [] as $index) {
|
||||
$this->indexes[] = $index;
|
||||
}
|
||||
|
||||
parent::__construct(...$validators);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if indexed array $indexes matches $queries
|
||||
*
|
||||
* @param array $indexes
|
||||
* @param array $queries
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function arrayMatch(array $indexes, array $queries): bool
|
||||
{
|
||||
// Check the count of indexes first for performance
|
||||
if (count($queries) !== count($indexes)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Sort them for comparison, the order is not important here anymore.
|
||||
sort($indexes, SORT_STRING);
|
||||
sort($queries, SORT_STRING);
|
||||
|
||||
// Only matching arrays will have equal diffs in both directions
|
||||
if (array_diff_assoc($indexes, $queries) !== array_diff_assoc($queries, $indexes)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is valid.
|
||||
*
|
||||
* Returns false if:
|
||||
* 1. any query in $value is invalid based on $validator
|
||||
* 2. there is no index with an exact match of the filters
|
||||
* 3. there is no index with an exact match of the order attributes
|
||||
*
|
||||
* Otherwise, returns true.
|
||||
*
|
||||
* @param mixed $value
|
||||
* @return bool
|
||||
*/
|
||||
public function isValid($value): bool
|
||||
{
|
||||
if (!parent::isValid($value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$queries = [];
|
||||
foreach ($value as $query) {
|
||||
if (!$query instanceof Query) {
|
||||
$query = Query::parse($query);
|
||||
}
|
||||
|
||||
$queries[] = $query;
|
||||
}
|
||||
|
||||
$grouped = Query::groupByType($queries);
|
||||
/** @var Query[] */ $filters = $grouped['filters'];
|
||||
/** @var string[] */ $orderAttributes = $grouped['orderAttributes'];
|
||||
|
||||
// Check filter queries for exact index match
|
||||
if (count($filters) > 0) {
|
||||
$filtersByAttribute = [];
|
||||
foreach ($filters as $filter) {
|
||||
$filtersByAttribute[$filter->getAttribute()] = $filter->getMethod();
|
||||
}
|
||||
|
||||
$found = null;
|
||||
|
||||
foreach ($this->indexes as $index) {
|
||||
if ($this->arrayMatch($index->getAttribute('attributes'), array_keys($filtersByAttribute))) {
|
||||
$found = $index;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$found) {
|
||||
$this->message = 'Index not found: ' . implode(",", array_keys($filtersByAttribute));
|
||||
return false;
|
||||
}
|
||||
|
||||
// search method requires fulltext index
|
||||
if (in_array(Query::TYPE_SEARCH, array_values($filtersByAttribute)) && $found['type'] !== Database::INDEX_FULLTEXT) {
|
||||
$this->message = 'Search method requires fulltext index: ' . implode(",", array_keys($filtersByAttribute));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check order attributes for exact index match
|
||||
$validator = new OrderAttributes($this->attributes, $this->indexes, true);
|
||||
if (count($orderAttributes) > 0 && !$validator->isValid($orderAttributes)) {
|
||||
$this->message = $validator->getDescription();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -2,25 +2,140 @@
|
|||
|
||||
namespace Appwrite\Utopia\Database\Validator;
|
||||
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Queries as ValidatorQueries;
|
||||
use Appwrite\Utopia\Database\Validator\Query\Base;
|
||||
use Utopia\Validator;
|
||||
use Utopia\Database\Query;
|
||||
|
||||
class Queries extends ValidatorQueries
|
||||
class Queries extends Validator
|
||||
{
|
||||
/**
|
||||
* Expression constructor
|
||||
*
|
||||
* @param Document[] $attributes
|
||||
* @param Document[] $indexes
|
||||
* @param bool $strict
|
||||
* @var string
|
||||
*/
|
||||
public function __construct($attributes, $indexes, $strict)
|
||||
{
|
||||
// Remove failed/stuck/processing indexes
|
||||
$indexes = \array_filter($indexes, function ($index) {
|
||||
return $index->getAttribute('status') === 'available';
|
||||
});
|
||||
protected $message = 'Invalid queries';
|
||||
|
||||
parent::__construct($attributes, $indexes, $strict);
|
||||
/**
|
||||
* @var Base[]
|
||||
*/
|
||||
protected $validators;
|
||||
|
||||
/**
|
||||
* Queries constructor
|
||||
*
|
||||
* @param Base ...$validators a list of validators
|
||||
*/
|
||||
public function __construct(Base ...$validators)
|
||||
{
|
||||
$this->validators = $validators;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Description.
|
||||
*
|
||||
* Returns validator description
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
return $this->message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is valid.
|
||||
*
|
||||
* Returns false if:
|
||||
* 1. any query in $value is invalid based on $validator
|
||||
*
|
||||
* Otherwise, returns true.
|
||||
*
|
||||
* @param mixed $value
|
||||
* @return bool
|
||||
*/
|
||||
public function isValid($value): bool
|
||||
{
|
||||
foreach ($value as $query) {
|
||||
if (!$query instanceof Query) {
|
||||
try {
|
||||
$query = Query::parse($query);
|
||||
} catch (\Throwable $th) {
|
||||
$this->message = 'Invalid query: ${query}';
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$method = $query->getMethod();
|
||||
$methodType = '';
|
||||
switch ($method) {
|
||||
case Query::TYPE_LIMIT:
|
||||
$methodType = Base::METHOD_TYPE_LIMIT;
|
||||
break;
|
||||
case Query::TYPE_OFFSET:
|
||||
$methodType = Base::METHOD_TYPE_OFFSET;
|
||||
break;
|
||||
case Query::TYPE_CURSORAFTER:
|
||||
case Query::TYPE_CURSORBEFORE:
|
||||
$methodType = Base::METHOD_TYPE_CURSOR;
|
||||
break;
|
||||
case Query::TYPE_ORDERASC:
|
||||
case Query::TYPE_ORDERDESC:
|
||||
$methodType = Base::METHOD_TYPE_ORDER;
|
||||
break;
|
||||
case Query::TYPE_EQUAL:
|
||||
case Query::TYPE_NOTEQUAL:
|
||||
case Query::TYPE_LESSER:
|
||||
case Query::TYPE_LESSEREQUAL:
|
||||
case Query::TYPE_GREATER:
|
||||
case Query::TYPE_GREATEREQUAL:
|
||||
case Query::TYPE_SEARCH:
|
||||
$methodType = Base::METHOD_TYPE_FILTER;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
$methodIsValid = false;
|
||||
foreach ($this->validators as $validator) {
|
||||
if ($validator->getMethodType() !== $methodType) {
|
||||
continue;
|
||||
}
|
||||
if (!$validator->isValid($query)) {
|
||||
$this->message = 'Query not valid: ' . $validator->getDescription();
|
||||
return false;
|
||||
}
|
||||
|
||||
$methodIsValid = true;
|
||||
}
|
||||
|
||||
if (!$methodIsValid) {
|
||||
$this->message = 'Query method not valid: ' . $method;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is array
|
||||
*
|
||||
* Function will return true if object is array.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isArray(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Type
|
||||
*
|
||||
* Returns validator type.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return self::TYPE_OBJECT;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,86 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Database\Validator\Queries;
|
||||
|
||||
use Appwrite\Utopia\Database\Validator\IndexedQueries;
|
||||
use Appwrite\Utopia\Database\Validator\Query\Limit;
|
||||
use Appwrite\Utopia\Database\Validator\Query\Offset;
|
||||
use Appwrite\Utopia\Database\Validator\Query\Cursor;
|
||||
use Appwrite\Utopia\Database\Validator\Query\Filter;
|
||||
use Appwrite\Utopia\Database\Validator\Query\Order;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
|
||||
class Collection extends IndexedQueries
|
||||
{
|
||||
/**
|
||||
* Expression constructor
|
||||
*
|
||||
* @param string $collection
|
||||
* @param string[] $allowedAttributes
|
||||
*/
|
||||
public function __construct(string $collection, array $allowedAttributes)
|
||||
{
|
||||
$collection = Config::getParam('collections', [])[$collection];
|
||||
// array for constant lookup time
|
||||
$allowedAttributesLookup = [];
|
||||
foreach ($allowedAttributes as $attribute) {
|
||||
$allowedAttributesLookup[$attribute] = true;
|
||||
}
|
||||
|
||||
$attributes = [];
|
||||
foreach ($collection['attributes'] as $attribute) {
|
||||
$key = $attribute['$id'];
|
||||
if (!isset($allowedAttributesLookup[$key])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$attributes[] = new Document([
|
||||
'key' => $key,
|
||||
'type' => $attribute['type'],
|
||||
'array' => $attribute['array'],
|
||||
]);
|
||||
}
|
||||
|
||||
$attributes[] = new Document([
|
||||
'key' => '$id',
|
||||
'type' => Database::VAR_STRING,
|
||||
'array' => false,
|
||||
]);
|
||||
$attributes[] = new Document([
|
||||
'$id' => '$createdAt',
|
||||
'type' => Database::VAR_DATETIME,
|
||||
'array' => false,
|
||||
]);
|
||||
$attributes[] = new Document([
|
||||
'$id' => '$updatedAt',
|
||||
'type' => Database::VAR_DATETIME,
|
||||
'array' => false,
|
||||
]);
|
||||
|
||||
$indexes = [];
|
||||
foreach ($allowedAttributes as $attribute) {
|
||||
$indexes[] = new Document([
|
||||
'status' => 'available',
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => [$attribute]
|
||||
]);
|
||||
}
|
||||
$indexes[] = new Document([
|
||||
'status' => 'available',
|
||||
'type' => Database::INDEX_FULLTEXT,
|
||||
'attributes' => ['search']
|
||||
]);
|
||||
|
||||
$validators = [
|
||||
new Limit(),
|
||||
new Offset(),
|
||||
new Cursor(),
|
||||
new Filter($attributes),
|
||||
new Order($attributes),
|
||||
];
|
||||
|
||||
parent::__construct($attributes, $indexes, ...$validators);
|
||||
}
|
||||
}
|
||||
29
src/Appwrite/Utopia/Database/Validator/Queries/Users.php
Normal file
29
src/Appwrite/Utopia/Database/Validator/Queries/Users.php
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Database\Validator\Queries;
|
||||
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Collection;
|
||||
|
||||
class Users extends Collection
|
||||
{
|
||||
public const ALLOWED_ATTRIBUTES = [
|
||||
'name',
|
||||
'email',
|
||||
'phone',
|
||||
'status',
|
||||
'passwordUpdate',
|
||||
'registration',
|
||||
'emailVerification',
|
||||
'phoneVerification',
|
||||
'search',
|
||||
];
|
||||
|
||||
/**
|
||||
* Expression constructor
|
||||
*
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct('users', self::ALLOWED_ATTRIBUTES);
|
||||
}
|
||||
}
|
||||
61
src/Appwrite/Utopia/Database/Validator/Query/Base.php
Normal file
61
src/Appwrite/Utopia/Database/Validator/Query/Base.php
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Database\Validator\Query;
|
||||
|
||||
use Utopia\Validator;
|
||||
use Utopia\Database\Query;
|
||||
|
||||
abstract class Base extends Validator
|
||||
{
|
||||
public const METHOD_TYPE_LIMIT = 'limit';
|
||||
public const METHOD_TYPE_OFFSET = 'offset';
|
||||
public const METHOD_TYPE_CURSOR = 'cursor';
|
||||
public const METHOD_TYPE_ORDER = 'order';
|
||||
public const METHOD_TYPE_FILTER = 'filter';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $message = 'Invalid query';
|
||||
|
||||
/**
|
||||
* Get Description.
|
||||
*
|
||||
* Returns validator description
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
return $this->message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is array
|
||||
*
|
||||
* Function will return true if object is array.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isArray(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Type
|
||||
*
|
||||
* Returns validator type.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return self::TYPE_OBJECT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns what type of query this Validator is for
|
||||
*/
|
||||
abstract public function getMethodType(): string;
|
||||
}
|
||||
44
src/Appwrite/Utopia/Database/Validator/Query/Cursor.php
Normal file
44
src/Appwrite/Utopia/Database/Validator/Query/Cursor.php
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Database\Validator\Query;
|
||||
|
||||
use Appwrite\Utopia\Database\Validator\Query\Base;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\UID;
|
||||
|
||||
class Cursor extends Base
|
||||
{
|
||||
/**
|
||||
* Is valid.
|
||||
*
|
||||
* Returns true if method is cursorBefore or cursorAfter and value is not null
|
||||
*
|
||||
* Otherwise, returns false
|
||||
*
|
||||
* @param Query $value
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isValid($query): bool
|
||||
{
|
||||
// Validate method
|
||||
$method = $query->getMethod();
|
||||
|
||||
if ($method === Query::TYPE_CURSORAFTER || $method === Query::TYPE_CURSORBEFORE) {
|
||||
$cursor = $query->getValue();
|
||||
$validator = new UID();
|
||||
if ($validator->isValid($cursor)) {
|
||||
return true;
|
||||
}
|
||||
$this->message = 'Invalid cursor: ' . $validator->getDescription();
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getMethodType(): string
|
||||
{
|
||||
return self::METHOD_TYPE_CURSOR;
|
||||
}
|
||||
}
|
||||
114
src/Appwrite/Utopia/Database/Validator/Query/Filter.php
Normal file
114
src/Appwrite/Utopia/Database/Validator/Query/Filter.php
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Database\Validator\Query;
|
||||
|
||||
use Appwrite\Utopia\Database\Validator\Query\Base;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Query;
|
||||
|
||||
class Filter extends Base
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $message = 'Invalid query';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $schema = [];
|
||||
|
||||
/**
|
||||
* Query constructor
|
||||
*
|
||||
* @param int $maxValuesCount
|
||||
*/
|
||||
public function __construct(array $attributes = [], int $maxValuesCount = 100)
|
||||
{
|
||||
foreach ($attributes as $attribute) {
|
||||
$this->schema[$attribute->getAttribute('key')] = $attribute->getArrayCopy();
|
||||
}
|
||||
|
||||
$this->maxValuesCount = $maxValuesCount;
|
||||
}
|
||||
|
||||
protected function isValidAttribute($attribute): bool
|
||||
{
|
||||
// Search for attribute in schema
|
||||
if (!isset($this->schema[$attribute])) {
|
||||
$this->message = 'Attribute not found in schema: ' . $attribute;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function isValidAttributeAndValues(string $attribute, array $values): bool
|
||||
{
|
||||
if (!$this->isValidAttribute($attribute)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$attributeSchema = $this->schema[$attribute];
|
||||
|
||||
if (count($values) > $this->maxValuesCount) {
|
||||
$this->message = 'Query on attribute has greater than ' . $this->maxValuesCount . ' values: ' . $attribute;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Extract the type of desired attribute from collection $schema
|
||||
$attributeType = $attributeSchema['type'];
|
||||
|
||||
foreach ($values as $value) {
|
||||
$condition = match ($attributeType) {
|
||||
Database::VAR_DATETIME => gettype($value) === Database::VAR_STRING,
|
||||
default => gettype($value) === $attributeType
|
||||
};
|
||||
|
||||
if (!$condition) {
|
||||
$this->message = 'Query type does not match expected: ' . $attributeType;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is valid.
|
||||
*
|
||||
* Returns true if method is a filter method, attribute exists, and value matches attribute type
|
||||
*
|
||||
* Otherwise, returns false
|
||||
*
|
||||
* @param Query $value
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isValid($query): bool
|
||||
{
|
||||
// Validate method
|
||||
$method = $query->getMethod();
|
||||
$attribute = $query->getAttribute();
|
||||
|
||||
switch ($method) {
|
||||
case Query::TYPE_EQUAL:
|
||||
case Query::TYPE_NOTEQUAL:
|
||||
case Query::TYPE_LESSER:
|
||||
case Query::TYPE_LESSEREQUAL:
|
||||
case Query::TYPE_GREATER:
|
||||
case Query::TYPE_GREATEREQUAL:
|
||||
case Query::TYPE_SEARCH:
|
||||
$values = $query->getValues();
|
||||
return $this->isValidAttributeAndValues($attribute, $values);
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function getMethodType(): string
|
||||
{
|
||||
return self::METHOD_TYPE_FILTER;
|
||||
}
|
||||
}
|
||||
61
src/Appwrite/Utopia/Database/Validator/Query/Limit.php
Normal file
61
src/Appwrite/Utopia/Database/Validator/Query/Limit.php
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Database\Validator\Query;
|
||||
|
||||
use Appwrite\Utopia\Database\Validator\Query\Base;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Validator\Range;
|
||||
|
||||
class Limit extends Base
|
||||
{
|
||||
protected int $maxLimit;
|
||||
|
||||
/**
|
||||
* Query constructor
|
||||
*
|
||||
* @param int $maxLimit
|
||||
*/
|
||||
public function __construct(int $maxLimit = 100)
|
||||
{
|
||||
$this->maxLimit = $maxLimit;
|
||||
}
|
||||
|
||||
protected function isValidLimit($limit): bool
|
||||
{
|
||||
$validator = new Range(0, $this->maxLimit);
|
||||
if ($validator->isValid($limit)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->message = 'Invalid limit: ' . $validator->getDescription();
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is valid.
|
||||
*
|
||||
* Returns true if method is limit values are within range.
|
||||
*
|
||||
* @param Query $value
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isValid($query): bool
|
||||
{
|
||||
// Validate method
|
||||
$method = $query->getMethod();
|
||||
|
||||
if ($method !== Query::TYPE_LIMIT) {
|
||||
$this->message = 'Query method invalid: ' . $method;
|
||||
return false;
|
||||
}
|
||||
|
||||
$limit = $query->getValue();
|
||||
return $this->isValidLimit($limit);
|
||||
}
|
||||
|
||||
public function getMethodType(): string
|
||||
{
|
||||
return self::METHOD_TYPE_LIMIT;
|
||||
}
|
||||
}
|
||||
61
src/Appwrite/Utopia/Database/Validator/Query/Offset.php
Normal file
61
src/Appwrite/Utopia/Database/Validator/Query/Offset.php
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Database\Validator\Query;
|
||||
|
||||
use Appwrite\Utopia\Database\Validator\Query\Base;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Validator\Range;
|
||||
|
||||
class Offset extends Base
|
||||
{
|
||||
protected int $maxOffset;
|
||||
|
||||
/**
|
||||
* Query constructor
|
||||
*
|
||||
* @param int $maxOffset
|
||||
*/
|
||||
public function __construct(int $maxOffset = 5000)
|
||||
{
|
||||
$this->maxOffset = $maxOffset;
|
||||
}
|
||||
|
||||
protected function isValidOffset($offset): bool
|
||||
{
|
||||
$validator = new Range(0, $this->maxOffset);
|
||||
if ($validator->isValid($offset)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->message = 'Invalid offset: ' . $validator->getDescription();
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is valid.
|
||||
*
|
||||
* Returns true if method is offset and values are within range.
|
||||
*
|
||||
* @param Query $value
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isValid($query): bool
|
||||
{
|
||||
// Validate method
|
||||
$method = $query->getMethod();
|
||||
|
||||
if ($method !== Query::TYPE_OFFSET) {
|
||||
$this->message = 'Query method invalid: ' . $method;
|
||||
return false;
|
||||
}
|
||||
|
||||
$offset = $query->getValue();
|
||||
return $this->isValidOffset($offset);
|
||||
}
|
||||
|
||||
public function getMethodType(): string
|
||||
{
|
||||
return self::METHOD_TYPE_OFFSET;
|
||||
}
|
||||
}
|
||||
68
src/Appwrite/Utopia/Database/Validator/Query/Order.php
Normal file
68
src/Appwrite/Utopia/Database/Validator/Query/Order.php
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Database\Validator\Query;
|
||||
|
||||
use Appwrite\Utopia\Database\Validator\Query\Base;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Validator;
|
||||
|
||||
class Order extends Base
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $schema = [];
|
||||
|
||||
/**
|
||||
* Query constructor
|
||||
*
|
||||
*/
|
||||
public function __construct(array $attributes = [])
|
||||
{
|
||||
foreach ($attributes as $attribute) {
|
||||
$this->schema[$attribute->getAttribute('key')] = $attribute->getArrayCopy();
|
||||
}
|
||||
}
|
||||
|
||||
protected function isValidAttribute($attribute): bool
|
||||
{
|
||||
// Search for attribute in schema
|
||||
if (!isset($this->schema[$attribute])) {
|
||||
$this->message = 'Attribute not found in schema: ' . $attribute;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is valid.
|
||||
*
|
||||
* Returns true if method is ORDER_ASC or ORDER_DESC and attributes are valid
|
||||
*
|
||||
* Otherwise, returns false
|
||||
*
|
||||
* @param Query $value
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isValid($query): bool
|
||||
{
|
||||
$method = $query->getMethod();
|
||||
$attribute = $query->getAttribute();
|
||||
|
||||
if ($method === Query::TYPE_ORDERASC || $method === Query::TYPE_ORDERDESC) {
|
||||
if ($attribute === '') {
|
||||
return true;
|
||||
}
|
||||
return $this->isValidAttribute($attribute);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getMethodType(): string
|
||||
{
|
||||
return self::METHOD_TYPE_ORDER;
|
||||
}
|
||||
}
|
||||
|
|
@ -28,6 +28,7 @@ use Appwrite\Utopia\Response\Model\AttributeEmail;
|
|||
use Appwrite\Utopia\Response\Model\AttributeEnum;
|
||||
use Appwrite\Utopia\Response\Model\AttributeIP;
|
||||
use Appwrite\Utopia\Response\Model\AttributeURL;
|
||||
use Appwrite\Utopia\Response\Model\AttributeDatetime;
|
||||
use Appwrite\Utopia\Response\Model\BaseList;
|
||||
use Appwrite\Utopia\Response\Model\Collection;
|
||||
use Appwrite\Utopia\Response\Model\Database;
|
||||
|
|
@ -126,6 +127,7 @@ class Response extends SwooleResponse
|
|||
public const MODEL_ATTRIBUTE_ENUM = 'attributeEnum';
|
||||
public const MODEL_ATTRIBUTE_IP = 'attributeIp';
|
||||
public const MODEL_ATTRIBUTE_URL = 'attributeUrl';
|
||||
public const MODEL_ATTRIBUTE_DATETIME = 'attributeDatetime';
|
||||
|
||||
// Users
|
||||
public const MODEL_ACCOUNT = 'account';
|
||||
|
|
@ -275,6 +277,7 @@ class Response extends SwooleResponse
|
|||
->setModel(new AttributeEnum())
|
||||
->setModel(new AttributeIP())
|
||||
->setModel(new AttributeURL())
|
||||
->setModel(new AttributeDatetime())
|
||||
->setModel(new Index())
|
||||
->setModel(new ModelDocument())
|
||||
->setModel(new Log())
|
||||
|
|
|
|||
|
|
@ -278,7 +278,7 @@ class V11 extends Filter
|
|||
$content['rules'] = \array_map(function ($attribute) use ($content) {
|
||||
return [
|
||||
'$id' => $attribute['key'],
|
||||
'$collection' => $content['$id'],
|
||||
'$collection' => ID::custom($content['$id']),
|
||||
'type' => $attribute['type'],
|
||||
'key' => $attribute['key'],
|
||||
'label' => $attribute['key'],
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ abstract class Model
|
|||
public const TYPE_FLOAT = 'double';
|
||||
public const TYPE_BOOLEAN = 'boolean';
|
||||
public const TYPE_JSON = 'json';
|
||||
public const TYPE_DATETIME = 'datetime';
|
||||
public const TYPE_DATETIME_EXAMPLE = '2020-10-15T06:38:00.000+00:00';
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
|
|
|
|||
68
src/Appwrite/Utopia/Response/Model/AttributeDatetime.php
Normal file
68
src/Appwrite/Utopia/Response/Model/AttributeDatetime.php
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Response\Model;
|
||||
|
||||
use Appwrite\Utopia\Response;
|
||||
|
||||
class AttributeDatetime extends Attribute
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this
|
||||
->addRule('key', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Attribute Key.',
|
||||
'default' => '',
|
||||
'example' => 'birthDay',
|
||||
])
|
||||
->addRule('type', [
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'Attribute type.',
|
||||
'default' => '',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
->addRule('format', [
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'Datetime format.',
|
||||
'default' => APP_DATABASE_ATTRIBUTE_DATETIME,
|
||||
'example' => APP_DATABASE_ATTRIBUTE_DATETIME,
|
||||
'array' => false,
|
||||
'require' => true,
|
||||
])
|
||||
->addRule('default', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Default value for attribute when not provided. Only null is optional',
|
||||
'default' => null,
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
'array' => false,
|
||||
'require' => false,
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
public array $conditions = [
|
||||
'type' => self::TYPE_DATETIME
|
||||
];
|
||||
|
||||
/**
|
||||
* Get Name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return 'AttributeDatetime';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return Response::MODEL_ATTRIBUTE_DATETIME;
|
||||
}
|
||||
}
|
||||
|
|
@ -26,6 +26,7 @@ class AttributeList extends Model
|
|||
Response::MODEL_ATTRIBUTE_ENUM,
|
||||
Response::MODEL_ATTRIBUTE_URL,
|
||||
Response::MODEL_ATTRIBUTE_IP,
|
||||
Response::MODEL_ATTRIBUTE_DATETIME,
|
||||
Response::MODEL_ATTRIBUTE_STRING // needs to be last, since its condition would dominate any other string attribute
|
||||
],
|
||||
'description' => 'List of attributes.',
|
||||
|
|
|
|||
|
|
@ -17,36 +17,29 @@ class Bucket extends Model
|
|||
'example' => '5e5ea5c16897e',
|
||||
])
|
||||
->addRule('$createdAt', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Bucket creation date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'Bucket creation time in Datetime',
|
||||
'default' => '',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
->addRule('$updatedAt', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Bucket update date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
])
|
||||
->addRule('$read', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'File read permissions.',
|
||||
'default' => [],
|
||||
'example' => ['role:all'],
|
||||
'array' => true,
|
||||
])
|
||||
->addRule('$write', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'File write permissions.',
|
||||
'default' => [],
|
||||
'example' => ['user:608f9da25e7e1'],
|
||||
'array' => true,
|
||||
])
|
||||
->addRule('permission', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Bucket permission model. Possible values: `bucket` or `file`',
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'Bucket update date in Datetime',
|
||||
'default' => '',
|
||||
'example' => 'file',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
->addRule('$permissions', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Bucket permissions. [Learn more about permissions](/docs/permissions).',
|
||||
'default' => [],
|
||||
'example' => ['read("any")'],
|
||||
'array' => true,
|
||||
])
|
||||
->addRule('fileSecurity', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Whether file-level security is enabled. [Learn more about permissions](/docs/permissions).',
|
||||
'default' => '',
|
||||
'example' => true,
|
||||
])
|
||||
->addRule('name', [
|
||||
'type' => self::TYPE_STRING,
|
||||
|
|
|
|||
|
|
@ -16,12 +16,6 @@ class Build extends Model
|
|||
'default' => '',
|
||||
'example' => '5e5ea5c16897e',
|
||||
])
|
||||
->addRule('startTime', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'The deployment creation date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
])
|
||||
->addRule('deploymentId', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'The deployment that created this build.',
|
||||
|
|
@ -51,11 +45,17 @@ class Build extends Model
|
|||
'default' => '',
|
||||
'example' => '',
|
||||
])
|
||||
->addRule('startTime', [
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'The deployment creation date in Datetime.',
|
||||
'default' => '',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
->addRule('endTime', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'The time the build was finished in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 0,
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'The time the build was finished in Datetime.',
|
||||
'default' => '',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
->addRule('duration', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
|
|
|
|||
|
|
@ -17,29 +17,22 @@ class Collection extends Model
|
|||
'example' => '5e5ea5c16897e',
|
||||
])
|
||||
->addRule('$createdAt', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Collection creation date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'Collection creation date in Datetime',
|
||||
'default' => '',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
->addRule('$updatedAt', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Collection update date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
])
|
||||
->addRule('$read', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Collection read permissions.',
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'Collection update date in Datetime',
|
||||
'default' => '',
|
||||
'example' => 'role:all',
|
||||
'array' => true
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
->addRule('$write', [
|
||||
->addRule('$permissions', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Collection write permissions.',
|
||||
'description' => 'Collection permissions. [Learn more about permissions](/docs/permissions).',
|
||||
'default' => '',
|
||||
'example' => 'user:608f9da25e7e1',
|
||||
'example' => ['read("any")'],
|
||||
'array' => true
|
||||
])
|
||||
->addRule('databaseId', [
|
||||
|
|
@ -60,11 +53,11 @@ class Collection extends Model
|
|||
'default' => true,
|
||||
'example' => false,
|
||||
])
|
||||
->addRule('permission', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Collection permission model. Possible values: `document` or `collection`',
|
||||
->addRule('documentSecurity', [
|
||||
'type' => self::TYPE_BOOLEAN,
|
||||
'description' => 'Whether document-level permissions are enabled. [Learn more about permissions](/docs/permissions).',
|
||||
'default' => '',
|
||||
'example' => 'document',
|
||||
'example' => true,
|
||||
])
|
||||
->addRule('attributes', [
|
||||
'type' => [
|
||||
|
|
@ -75,6 +68,7 @@ class Collection extends Model
|
|||
Response::MODEL_ATTRIBUTE_ENUM,
|
||||
Response::MODEL_ATTRIBUTE_URL,
|
||||
Response::MODEL_ATTRIBUTE_IP,
|
||||
Response::MODEL_ATTRIBUTE_DATETIME,
|
||||
Response::MODEL_ATTRIBUTE_STRING, // needs to be last, since its condition would dominate any other string attribute
|
||||
],
|
||||
'description' => 'Collection attributes.',
|
||||
|
|
|
|||
|
|
@ -17,16 +17,16 @@ class Deployment extends Model
|
|||
'example' => '5e5ea5c16897e',
|
||||
])
|
||||
->addRule('$createdAt', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Deployment creation date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'Deployment creation date in Datetime',
|
||||
'default' => '',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
->addRule('$updatedAt', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Deployment update date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'Deployment update date in Datetime',
|
||||
'default' => '',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
->addRule('resourceId', [
|
||||
'type' => self::TYPE_STRING,
|
||||
|
|
|
|||
|
|
@ -43,29 +43,22 @@ class Document extends Any
|
|||
'example' => '5e5ea5c15117e',
|
||||
])
|
||||
->addRule('$createdAt', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Document creation date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'Document creation date in Datetime',
|
||||
'default' => '',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
->addRule('$updatedAt', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Document update date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
])
|
||||
->addRule('$read', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Document read permissions.',
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'Document update date in Datetime',
|
||||
'default' => '',
|
||||
'example' => 'role:all',
|
||||
'array' => true,
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
->addRule('$write', [
|
||||
->addRule('$permissions', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Document write permissions.',
|
||||
'description' => 'Document permissions. [Learn more about permissions](/docs/permissions).',
|
||||
'default' => '',
|
||||
'example' => 'user:608f9da25e7e1',
|
||||
'example' => ['read("any")'],
|
||||
'array' => true,
|
||||
])
|
||||
;
|
||||
|
|
|
|||
|
|
@ -22,16 +22,16 @@ class Domain extends Model
|
|||
'example' => '5e5ea5c16897e',
|
||||
])
|
||||
->addRule('$createdAt', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Domain creation date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'Domain creation date in Datetime',
|
||||
'default' => '',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
->addRule('$updatedAt', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Domain update date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'Domain update date in Datetime',
|
||||
'default' => '',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
->addRule('domain', [
|
||||
'type' => self::TYPE_STRING,
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue