mirror of
https://github.com/appwrite/appwrite
synced 2026-05-22 00:18:25 +00:00
Merge branch 'refactor-permissions-inc-console-fix' of github.com:appwrite/appwrite into datetime-jake-tz
Conflicts: composer.json composer.lock
This commit is contained in:
commit
f7948285f7
123 changed files with 2696 additions and 2773 deletions
File diff suppressed because it is too large
Load diff
|
|
@ -80,7 +80,7 @@ return [
|
|||
'label' => 'Owner',
|
||||
'scopes' => \array_merge($member, $admins, []),
|
||||
],
|
||||
Auth::USER_ROLE_APP => [
|
||||
Auth::USER_ROLE_APPS => [
|
||||
'label' => 'Application',
|
||||
'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,8 +6,11 @@ use Appwrite\Auth\Phone;
|
|||
use Appwrite\Auth\Validator\Password;
|
||||
use Appwrite\Auth\Validator\Phone as ValidatorPhone;
|
||||
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;
|
||||
|
|
@ -15,25 +18,24 @@ use Appwrite\OpenSSL\OpenSSL;
|
|||
use Appwrite\Stats\Stats;
|
||||
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\Audit;
|
||||
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;
|
||||
|
|
@ -95,7 +97,7 @@ 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,
|
||||
'$permissions' => [
|
||||
|
|
@ -107,8 +109,8 @@ App::post('/v1/account')
|
|||
'emailVerification' => false,
|
||||
'status' => true,
|
||||
'password' => Auth::passwordHash($password),
|
||||
'passwordUpdate' => \time(),
|
||||
'registration' => \time(),
|
||||
'passwordUpdate' => DateTime::now(),
|
||||
'registration' => DateTime::now(),
|
||||
'reset' => false,
|
||||
'name' => $name,
|
||||
'prefs' => new \stdClass(),
|
||||
|
|
@ -169,7 +171,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'))) {
|
||||
throw new Exception('Invalid credentials', 401, Exception::USER_INVALID_CREDENTIALS); // Wrong password or username
|
||||
|
|
@ -181,17 +184,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']) : '--',
|
||||
|
|
@ -223,8 +226,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)
|
||||
;
|
||||
|
||||
|
|
@ -454,8 +457,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
|
||||
|
|
@ -468,7 +471,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;
|
||||
|
|
@ -482,7 +486,7 @@ 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,
|
||||
'$permissions' => [
|
||||
|
|
@ -494,8 +498,8 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
|
|||
'emailVerification' => true,
|
||||
'status' => true, // Email should already be authenticated by OAuth2 provider
|
||||
'password' => Auth::passwordHash(Auth::passwordGenerator()),
|
||||
'passwordUpdate' => 0,
|
||||
'registration' => \time(),
|
||||
'passwordUpdate' => null,
|
||||
'registration' => DateTime::now(),
|
||||
'reset' => false,
|
||||
'name' => $name,
|
||||
'prefs' => new \stdClass(),
|
||||
|
|
@ -518,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']) : '--',
|
||||
|
|
@ -596,8 +601,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'])
|
||||
;
|
||||
});
|
||||
|
|
@ -638,7 +643,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;
|
||||
|
|
@ -651,7 +656,7 @@ 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,
|
||||
|
|
@ -664,8 +669,8 @@ App::post('/v1/account/sessions/magic-url')
|
|||
'emailVerification' => false,
|
||||
'status' => true,
|
||||
'password' => null,
|
||||
'passwordUpdate' => 0,
|
||||
'registration' => \time(),
|
||||
'passwordUpdate' => null,
|
||||
'registration' => DateTime::now(),
|
||||
'reset' => false,
|
||||
'prefs' => new \stdClass(),
|
||||
'sessions' => null,
|
||||
|
|
@ -676,11 +681,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,
|
||||
|
|
@ -780,15 +784,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']) : '--',
|
||||
|
|
@ -840,8 +845,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)
|
||||
;
|
||||
|
||||
|
|
@ -888,7 +893,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, [$number])]);
|
||||
$user = $dbForProject->findOne('users', [Query::equal('phone', [$number])]);
|
||||
|
||||
if (!$user) {
|
||||
$limit = $project->getAttribute('auths', [])['limit'] ?? 0;
|
||||
|
|
@ -901,7 +906,7 @@ 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,
|
||||
|
|
@ -916,8 +921,8 @@ App::post('/v1/account/sessions/phone')
|
|||
'phoneVerification' => false,
|
||||
'status' => true,
|
||||
'password' => null,
|
||||
'passwordUpdate' => 0,
|
||||
'registration' => \time(),
|
||||
'passwordUpdate' => null,
|
||||
'registration' => DateTime::now(),
|
||||
'reset' => false,
|
||||
'prefs' => new \stdClass(),
|
||||
'sessions' => null,
|
||||
|
|
@ -928,11 +933,10 @@ App::post('/v1/account/sessions/phone')
|
|||
}
|
||||
|
||||
$secret = $phone->generateSecretDigits();
|
||||
|
||||
$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,
|
||||
|
|
@ -1019,15 +1023,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']) : '--',
|
||||
|
|
@ -1077,8 +1082,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)
|
||||
;
|
||||
|
||||
|
|
@ -1139,7 +1144,7 @@ App::post('/v1/account/sessions/anonymous')
|
|||
}
|
||||
}
|
||||
|
||||
$userId = $dbForProject->getId();
|
||||
$userId = ID::unique();
|
||||
$user = Authorization::skip(fn() => $dbForProject->createDocument('users', new Document([
|
||||
'$id' => $userId,
|
||||
'$permissions' => [
|
||||
|
|
@ -1151,8 +1156,8 @@ App::post('/v1/account/sessions/anonymous')
|
|||
'emailVerification' => false,
|
||||
'status' => true,
|
||||
'password' => null,
|
||||
'passwordUpdate' => 0,
|
||||
'registration' => \time(),
|
||||
'passwordUpdate' => null,
|
||||
'registration' => DateTime::now(),
|
||||
'reset' => false,
|
||||
'name' => null,
|
||||
'prefs' => new \stdClass(),
|
||||
|
|
@ -1167,15 +1172,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']) : '--',
|
||||
|
|
@ -1212,8 +1218,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)
|
||||
;
|
||||
|
||||
|
|
@ -1514,7 +1520,7 @@ App::patch('/v1/account/password')
|
|||
->action(function (string $password, string $oldPassword, Response $response, Document $user, Database $dbForProject, Audit $audits, Stats $usage, Event $events) {
|
||||
|
||||
// Check old password only if its an existing user.
|
||||
if ($user->getAttribute('passwordUpdate') !== 0 && !Auth::passwordVerify($oldPassword, $user->getAttribute('password'))) { // Double check user password
|
||||
if ($user->getAttribute('passwordUpdate') !== null && !Auth::passwordVerify($oldPassword, $user->getAttribute('password'))) { // Double check user password
|
||||
throw new Exception('Invalid credentials', 401, Exception::USER_INVALID_CREDENTIALS);
|
||||
}
|
||||
|
||||
|
|
@ -1523,7 +1529,7 @@ App::patch('/v1/account/password')
|
|||
$user->getId(),
|
||||
$user
|
||||
->setAttribute('password', Auth::passwordHash($password))
|
||||
->setAttribute('passwordUpdate', \time())
|
||||
->setAttribute('passwordUpdate', DateTime::now())
|
||||
);
|
||||
|
||||
$audits
|
||||
|
|
@ -1854,7 +1860,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);
|
||||
|
||||
|
|
@ -1986,7 +1992,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) {
|
||||
|
|
@ -1997,11 +2003,11 @@ App::post('/v1/account/recovery')
|
|||
throw new Exception('Invalid credentials. User is blocked', 401, 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,
|
||||
|
|
@ -2101,7 +2107,7 @@ App::put('/v1/account/recovery')
|
|||
|
||||
$profile = $dbForProject->updateDocument('users', $profile->getId(), $profile
|
||||
->setAttribute('password', Auth::passwordHash($password))
|
||||
->setAttribute('passwordUpdate', \time())
|
||||
->setAttribute('passwordUpdate', DateTime::now())
|
||||
->setAttribute('emailVerification', true));
|
||||
|
||||
$recoveryDocument = $dbForProject->getDocument('tokens', $recovery);
|
||||
|
|
@ -2159,13 +2165,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,
|
||||
|
|
@ -2316,14 +2320,12 @@ App::post('/v1/account/verification/phone')
|
|||
$roles = Authorization::getRoles();
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
|
||||
$isAppUser = Auth::isAppUser($roles);
|
||||
|
||||
$verificationSecret = Auth::tokenGenerator();
|
||||
|
||||
$secret = $phone->generateSecretDigits();
|
||||
$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,
|
||||
|
|
|
|||
|
|
@ -5,6 +5,9 @@ use Utopia\App;
|
|||
use Appwrite\Event\Delete;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Utopia\Audit\Audit;
|
||||
use Utopia\Database\Permission;
|
||||
use Utopia\Database\Validator\DatetimeValidator;
|
||||
use Utopia\Database\ID;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\FloatValidator;
|
||||
use Utopia\Validator\Integer;
|
||||
|
|
@ -15,12 +18,13 @@ use Utopia\Validator\ArrayList;
|
|||
use Utopia\Validator\JSON;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Adapter\MariaDB;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\Permissions;
|
||||
use Utopia\Database\Validator\QueryValidator;
|
||||
use Utopia\Database\Validator\Query as QueryValidator;
|
||||
use Utopia\Database\Validator\Structure;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Database\Exception\Authorization as AuthorizationException;
|
||||
|
|
@ -94,7 +98,7 @@ function createAttribute(string $databaseId, string $collectionId, Document $att
|
|||
|
||||
try {
|
||||
$attribute = new Document([
|
||||
'$id' => $db->getInternalId() . '_' . $collection->getInternalId() . '_' . $key,
|
||||
'$id' => ID::custom($db->getInternalId() . '_' . $collection->getInternalId() . '_' . $key),
|
||||
'key' => $key,
|
||||
'databaseInternalId' => $db->getInternalId(),
|
||||
'databaseId' => $db->getId(),
|
||||
|
|
@ -173,7 +177,7 @@ App::post('/v1/databases')
|
|||
->inject('events')
|
||||
->action(function (string $databaseId, string $name, Response $response, Database $dbForProject, EventAudit $audits, Stats $usage, Event $events) {
|
||||
|
||||
$databaseId = $databaseId == 'unique()' ? $dbForProject->getId() : $databaseId;
|
||||
$databaseId = $databaseId == 'unique()' ? ID::unique() : $databaseId;
|
||||
|
||||
try {
|
||||
$dbForProject->createDocument('databases', new Document([
|
||||
|
|
@ -247,31 +251,37 @@ App::get('/v1/databases')
|
|||
->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 collection used as the starting point for the query, excluding the collection 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')
|
||||
->inject('usage')
|
||||
->action(function (string $search, int $limit, int $offset, string $cursor, string $cursorDirection, string $orderType, Response $response, Database $dbForProject, Stats $usage) {
|
||||
|
||||
$filterQueries = [];
|
||||
|
||||
if (!empty($search)) {
|
||||
$filterQueries[] = Query::search('search', $search);
|
||||
}
|
||||
|
||||
$queries = [];
|
||||
$queries[] = Query::limit($limit);
|
||||
$queries[] = Query::offset($offset);
|
||||
$queries[] = $orderType === 'ASC' ? Query::orderAsc('') : Query::orderDesc('');
|
||||
if (!empty($cursor)) {
|
||||
$cursorDocument = $dbForProject->getDocument('databases', $cursor);
|
||||
|
||||
if ($cursorDocument->isEmpty()) {
|
||||
throw new Exception("Collection '{$cursor}' for the 'cursor' value not found.", 400, Exception::GENERAL_CURSOR_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
|
||||
$queries = [];
|
||||
|
||||
if (!empty($search)) {
|
||||
$queries[] = new Query('search', Query::TYPE_SEARCH, [$search]);
|
||||
$queries[] = $cursorDirection === Database::CURSOR_AFTER ? Query::cursorAfter($cursorDocument) : Query::cursorBefore($cursorDocument);
|
||||
}
|
||||
|
||||
$usage->setParam('databases.read', 1);
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'databases' => $dbForProject->find('databases', $queries, $limit, $offset, [], [$orderType], $cursorDocument ?? null, $cursorDirection),
|
||||
'total' => $dbForProject->count('databases', $queries, APP_LIMIT_COUNT),
|
||||
'databases' => $dbForProject->find('databases', \array_merge($filterQueries, $queries)),
|
||||
'total' => $dbForProject->count('databases', $filterQueries, APP_LIMIT_COUNT),
|
||||
]), Response::MODEL_DATABASE_LIST);
|
||||
});
|
||||
|
||||
|
|
@ -344,7 +354,7 @@ App::get('/v1/databases/:databaseId/logs')
|
|||
|
||||
$output[$i] = new Document([
|
||||
'event' => $log['event'],
|
||||
'userId' => $log['userId'],
|
||||
'userId' => ID::custom($log['userId']),
|
||||
'userEmail' => $log['data']['userEmail'] ?? null,
|
||||
'userName' => $log['data']['userName'] ?? null,
|
||||
'mode' => $log['data']['mode'] ?? null,
|
||||
|
|
@ -513,9 +523,7 @@ App::post('/v1/databases/:databaseId/collections')
|
|||
throw new Exception('Database not found', 404, Exception::DATABASE_NOT_FOUND);
|
||||
}
|
||||
|
||||
$collectionId = $collectionId == 'unique()' ? $dbForProject->getId() : $collectionId;
|
||||
|
||||
$permissions = PermissionsProcessor::handleAggregates($permissions);
|
||||
$collectionId = $collectionId == 'unique()' ? ID::unique() : $collectionId;
|
||||
|
||||
try {
|
||||
$dbForProject->createDocument('database_' . $database->getInternalId(), new Document([
|
||||
|
|
@ -573,7 +581,7 @@ App::get('/v1/databases/:databaseId/collections')
|
|||
->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 collection used as the starting point for the query, excluding the collection 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')
|
||||
->inject('usage')
|
||||
|
|
@ -585,18 +593,26 @@ App::get('/v1/databases/:databaseId/collections')
|
|||
throw new Exception('Database not found', 404, Exception::DATABASE_NOT_FOUND);
|
||||
}
|
||||
|
||||
if (!empty($cursor)) {
|
||||
$cursorCollection = $dbForProject->getDocument('database_' . $database->getInternalId(), $cursor);
|
||||
$filterQueries = [];
|
||||
|
||||
if ($cursorCollection->isEmpty()) {
|
||||
throw new Exception("Collection '{$cursor}' for the 'cursor' value not found.", 400, Exception::GENERAL_CURSOR_NOT_FOUND);
|
||||
}
|
||||
if (!empty($search)) {
|
||||
$filterQueries[] = Query::search('search', $search);
|
||||
}
|
||||
|
||||
$queries = [];
|
||||
$queries[] = Query::limit($limit);
|
||||
$queries[] = Query::offset($offset);
|
||||
$queries[] = $orderType === 'ASC' ? Query::orderAsc('') : Query::orderDesc('');
|
||||
if (!empty($cursor)) {
|
||||
$cursorDocument = $dbForProject->getDocument('database_' . $database->getInternalId(), $cursor);
|
||||
|
||||
if (!empty($search)) {
|
||||
$queries[] = new Query('search', Query::TYPE_SEARCH, [$search]);
|
||||
if ($cursorDocument->isEmpty()) {
|
||||
throw new Exception("Collection '{$cursor}' for the 'cursor' value not found.", 400, Exception::GENERAL_CURSOR_NOT_FOUND);
|
||||
}
|
||||
|
||||
$queries[] = $cursorDirection === Database::CURSOR_AFTER
|
||||
? Query::cursorAfter($cursorDocument)
|
||||
: Query::cursorBefore($cursorDocument);
|
||||
}
|
||||
|
||||
$usage
|
||||
|
|
@ -604,8 +620,8 @@ App::get('/v1/databases/:databaseId/collections')
|
|||
->setParam('databases.collections.read', 1);
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'collections' => $dbForProject->find('database_' . $database->getInternalId(), $queries, $limit, $offset, [], [$orderType], $cursorCollection ?? null, $cursorDirection),
|
||||
'total' => $dbForProject->count('database_' . $database->getInternalId(), $queries, APP_LIMIT_COUNT),
|
||||
'collections' => $dbForProject->find('database_' . $database->getInternalId(), \array_merge($filterQueries, $queries)),
|
||||
'total' => $dbForProject->count('database_' . $database->getInternalId(), $filterQueries, APP_LIMIT_COUNT),
|
||||
]), Response::MODEL_COLLECTION_LIST);
|
||||
});
|
||||
|
||||
|
|
@ -776,7 +792,7 @@ App::put('/v1/databases/:databaseId/collections/:collectionId')
|
|||
}
|
||||
|
||||
$permissions ??= $collection->getPermissions() ?? [];
|
||||
$permissions = PermissionsProcessor::handleAggregates($permissions);
|
||||
|
||||
$enabled ??= $collection->getAttribute('enabled', true);
|
||||
|
||||
try {
|
||||
|
|
@ -1283,6 +1299,49 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/boolea
|
|||
$response->dynamic($attribute, Response::MODEL_ATTRIBUTE_BOOLEAN);
|
||||
});
|
||||
|
||||
|
||||
App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/datetime')
|
||||
->alias('/v1/database/collections/:collectionId/attributes/datetime', ['databaseId' => 'default'])
|
||||
->desc('Create DateTime Attribute')
|
||||
->groups(['api', 'database'])
|
||||
->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create')
|
||||
->label('scope', 'collections.write')
|
||||
->label('sdk.namespace', 'databases')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.method', 'createDatetimeAttribute')
|
||||
->label('sdk.description', '/docs/references/databases/create-datetime-attribute.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_ACCEPTED)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_ATTRIBUTE_DATETIME)
|
||||
->param('databaseId', '', new UID(), 'Database ID.')
|
||||
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).')
|
||||
->param('key', '', new Key(), 'Attribute Key.')
|
||||
->param('required', null, new Boolean(), 'Is attribute required?')
|
||||
->param('default', null, new DatetimeValidator(), 'Default value for attribute when not provided. Cannot be set when attribute is required.', true)
|
||||
->param('array', false, new Boolean(), 'Is attribute an array?', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('database')
|
||||
->inject('audits')
|
||||
->inject('usage')
|
||||
->inject('events')
|
||||
->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?bool $default, bool $array, Response $response, Database $dbForProject, EventDatabase $database, EventAudit $audits, Stats $usage, Event $events) {
|
||||
|
||||
$attribute = createAttribute($databaseId, $collectionId, new Document([
|
||||
'key' => $key,
|
||||
'type' => Database::VAR_DATETIME,
|
||||
'size' => 0,
|
||||
'required' => $required,
|
||||
'default' => $default,
|
||||
'array' => $array,
|
||||
'filters' => ['datetime']
|
||||
]), $response, $dbForProject, $database, $audits, $events, $usage);
|
||||
|
||||
$response->setStatusCode(Response::STATUS_CODE_ACCEPTED);
|
||||
$response->dynamic($attribute, Response::MODEL_ATTRIBUTE_DATETIME);
|
||||
});
|
||||
|
||||
|
||||
App::get('/v1/databases/:databaseId/collections/:collectionId/attributes')
|
||||
->alias('/v1/database/collections/:collectionId/attributes', ['databaseId' => 'default'])
|
||||
->desc('List Attributes')
|
||||
|
|
@ -1337,6 +1396,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/attributes/:key')
|
|||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', [
|
||||
Response::MODEL_ATTRIBUTE_DATETIME,
|
||||
Response::MODEL_ATTRIBUTE_BOOLEAN,
|
||||
Response::MODEL_ATTRIBUTE_INTEGER,
|
||||
Response::MODEL_ATTRIBUTE_FLOAT,
|
||||
|
|
@ -1344,7 +1404,8 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/attributes/:key')
|
|||
Response::MODEL_ATTRIBUTE_ENUM,
|
||||
Response::MODEL_ATTRIBUTE_URL,
|
||||
Response::MODEL_ATTRIBUTE_IP,
|
||||
Response::MODEL_ATTRIBUTE_STRING,])// needs to be last, since its condition would dominate any other string attribute
|
||||
Response::MODEL_ATTRIBUTE_DATETIME,
|
||||
Response::MODEL_ATTRIBUTE_STRING])// needs to be last, since its condition would dominate any other string attribute
|
||||
->param('databaseId', '', new UID(), 'Database ID.')
|
||||
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
|
||||
->param('key', '', new Key(), 'Attribute Key.')
|
||||
|
|
@ -1376,6 +1437,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/attributes/:key')
|
|||
$format = $attribute->getAttribute('format');
|
||||
|
||||
$model = match ($type) {
|
||||
Database::VAR_DATETIME => Response::MODEL_ATTRIBUTE_DATETIME,
|
||||
Database::VAR_BOOLEAN => Response::MODEL_ATTRIBUTE_BOOLEAN,
|
||||
Database::VAR_INTEGER => Response::MODEL_ATTRIBUTE_INTEGER,
|
||||
Database::VAR_FLOAT => Response::MODEL_ATTRIBUTE_FLOAT,
|
||||
|
|
@ -1460,6 +1522,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/attributes/:key
|
|||
$format = $attribute->getAttribute('format');
|
||||
|
||||
$model = match ($type) {
|
||||
Database::VAR_DATETIME => Response::MODEL_ATTRIBUTE_DATETIME,
|
||||
Database::VAR_BOOLEAN => Response::MODEL_ATTRIBUTE_BOOLEAN,
|
||||
Database::VAR_INTEGER => Response::MODEL_ATTRIBUTE_INTEGER,
|
||||
Database::VAR_FLOAT => Response::MODEL_ATTRIBUTE_FLOAT,
|
||||
|
|
@ -1529,8 +1592,8 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/indexes')
|
|||
}
|
||||
|
||||
$count = $dbForProject->count('indexes', [
|
||||
new Query('collectionInternalId', Query::TYPE_EQUAL, [$collection->getInternalId()]),
|
||||
new Query('databaseInternalId', Query::TYPE_EQUAL, [$db->getInternalId()])
|
||||
Query::equal('collectionInternalId', [$collection->getInternalId()]),
|
||||
Query::equal('databaseInternalId', [$db->getInternalId()])
|
||||
], 61);
|
||||
|
||||
$limit = 64 - MariaDB::getNumberOfDefaultIndexes();
|
||||
|
|
@ -1554,7 +1617,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/indexes')
|
|||
|
||||
$oldAttributes[] = [
|
||||
'key' => '$createdAt',
|
||||
'type' => 'integer',
|
||||
'type' => 'string',
|
||||
'status' => 'available',
|
||||
'signed' => false,
|
||||
'required' => false,
|
||||
|
|
@ -1565,7 +1628,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/indexes')
|
|||
|
||||
$oldAttributes[] = [
|
||||
'key' => '$updatedAt',
|
||||
'type' => 'integer',
|
||||
'type' => 'string',
|
||||
'status' => 'available',
|
||||
'signed' => false,
|
||||
'required' => false,
|
||||
|
|
@ -1600,7 +1663,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/indexes')
|
|||
|
||||
try {
|
||||
$index = $dbForProject->createDocument('indexes', new Document([
|
||||
'$id' => $db->getInternalId() . '_' . $collection->getInternalId() . '_' . $key,
|
||||
'$id' => ID::custom($db->getInternalId() . '_' . $collection->getInternalId() . '_' . $key),
|
||||
'key' => $key,
|
||||
'status' => 'processing', // processing, available, failed, deleting, stuck
|
||||
'databaseInternalId' => $db->getInternalId(),
|
||||
|
|
@ -1831,7 +1894,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents')
|
|||
->param('documentId', '', new CustomId(), 'Document 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('collectionId', null, new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection). Make sure to define attributes before creating documents.')
|
||||
->param('data', [], new JSON(), 'Document data as JSON object.')
|
||||
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of strings with permissions. By default no user is granted with any 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, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE]), 'An array of strings with permissions. By default no user is granted with any permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('user')
|
||||
|
|
@ -1870,26 +1933,49 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents')
|
|||
throw new Exception('Unauthorized permissions', 401, Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
$permissions = PermissionsProcessor::addDefaultsIfNeeded(
|
||||
$permissions,
|
||||
$user->getId(),
|
||||
allowedPermissions: \array_filter(
|
||||
Database::PERMISSIONS,
|
||||
fn ($permission) => $permission !== Database::PERMISSION_CREATE
|
||||
),
|
||||
/**
|
||||
* Add permissions for current the user for any missing types
|
||||
* from the allowed permissions for this resource type.
|
||||
*/
|
||||
$allowedPermissions = \array_filter(
|
||||
Database::PERMISSIONS,
|
||||
fn ($permission) => $permission !== Database::PERMISSION_CREATE
|
||||
);
|
||||
|
||||
$permissions = PermissionsProcessor::handleAggregates($permissions);
|
||||
|
||||
if (!PermissionsProcessor::allowedForResourceType('document', $permissions)) {
|
||||
throw new Exception('Invalid permission', 400, Exception::GENERAL_PERMISSION_INVALID);
|
||||
if (\is_null($permissions)) {
|
||||
$permissions = [];
|
||||
if (!empty($user->getId())) {
|
||||
foreach ($allowedPermissions as $permission) {
|
||||
$permissions[] = (new Permission($permission, 'user', $user->getId()))->toString();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
foreach ($allowedPermissions as $permission) {
|
||||
// Default any missing allowed permissions to the current user
|
||||
if (empty(\preg_grep("#^{$permission}\(.+\)$#", $permissions)) && !empty($user->getId())) {
|
||||
$permissions[] = (new Permission($permission, 'user', $user->getId()))->toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!PermissionsProcessor::allowedForUserType($permissions)) {
|
||||
throw new Exception('Permissions must be one of: (' . \implode(', ', Authorization::getRoles()) . ')', 400, Exception::USER_UNAUTHORIZED);
|
||||
|
||||
// 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 (Database::PERMISSIONS as $type) {
|
||||
foreach ($permissions as $permission) {
|
||||
if (!\str_starts_with($permission, $type)) {
|
||||
continue;
|
||||
}
|
||||
$role = \str_replace([$type, '(', ')', '"', ' '], '', $permission);
|
||||
if (!Authorization::isRole($role)) {
|
||||
throw new Exception('Permissions must be one of: (' . \implode(', ', Authorization::getRoles()) . ')', 400, Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$data['$collection'] = $collection->getId(); // Adding this param to make API easier for developers
|
||||
$data['$id'] = $documentId == 'unique()' ? $dbForProject->getId() : $documentId;
|
||||
$data['$id'] = $documentId == 'unique()' ? ID::unique() : $documentId;
|
||||
$data['$permissions'] = $permissions;
|
||||
|
||||
try {
|
||||
|
|
@ -1944,7 +2030,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents')
|
|||
->param('cursor', '', new UID(), 'ID of the document used as the starting point for the query, excluding the document itself. Should be used for efficient pagination when working with large sets of data. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
|
||||
->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('orderAttributes', [], new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of attributes used to sort results. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' order attributes are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long.', true)
|
||||
->param('orderTypes', [], new ArrayList(new WhiteList(['DESC', 'ASC'], true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of order directions for sorting attribtues. Possible values are DESC for descending order, or ASC for ascending order. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' order types are allowed.', true)
|
||||
->param('orderTypes', [], new ArrayList(new WhiteList([Database::ORDER_DESC, Database::ORDER_ASC], true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of order directions for sorting attribtues. Possible values are DESC for descending order, or ASC for ascending order. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' order types are allowed.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('usage')
|
||||
|
|
@ -1972,48 +2058,46 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents')
|
|||
throw new Exception('Unauthorized permissions', 401, Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
$queries = \array_map(function ($query) {
|
||||
$query = Query::parse($query);
|
||||
|
||||
if (\count($query->getValues()) > 100) {
|
||||
throw new Exception("You cannot use more than 100 query values on attribute '{$query->getAttribute()}'", 400, Exception::GENERAL_QUERY_LIMIT_EXCEEDED);
|
||||
}
|
||||
|
||||
return $query;
|
||||
$filterQueries = \array_map(function ($query) {
|
||||
return Query::parse($query);
|
||||
}, $queries);
|
||||
|
||||
if (!empty($orderAttributes)) {
|
||||
$validator = new OrderAttributes($collection->getAttribute('attributes', []), $collection->getAttribute('indexes', []), true);
|
||||
if (!$validator->isValid($orderAttributes)) {
|
||||
throw new Exception($validator->getDescription(), 400, Exception::GENERAL_QUERY_INVALID);
|
||||
}
|
||||
$otherQueries = [];
|
||||
$otherQueries[] = Query::limit($limit);
|
||||
$otherQueries[] = Query::offset($offset);
|
||||
foreach ($orderTypes as $i => $orderType) {
|
||||
$otherQueries[] = $orderType === Database::ORDER_DESC ? Query::orderDesc($orderAttributes[$i] ?? '') : Query::orderAsc($orderAttributes[$i] ?? '');
|
||||
}
|
||||
|
||||
if (!empty($queries)) {
|
||||
$validator = new QueriesValidator(new QueryValidator($collection->getAttribute('attributes', [])), $collection->getAttribute('indexes', []), true);
|
||||
if (!$validator->isValid($queries)) {
|
||||
throw new Exception($validator->getDescription(), 400, Exception::GENERAL_QUERY_INVALID);
|
||||
}
|
||||
}
|
||||
|
||||
$cursorDocument = null;
|
||||
if (!empty($cursor)) {
|
||||
if ($documentSecurity) {
|
||||
$cursorDocument = Authorization::skip(fn() => $dbForProject->getDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $cursor));
|
||||
} else {
|
||||
$cursorDocument = $dbForProject->getDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $cursor);
|
||||
} else {
|
||||
$cursorDocument = Authorization::skip(fn() => $dbForProject->getDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $cursor));
|
||||
}
|
||||
if ($cursorDocument->isEmpty()) {
|
||||
throw new Exception("Document '{$cursor}' for the 'cursor' value not found.", 400, Exception::GENERAL_CURSOR_NOT_FOUND);
|
||||
}
|
||||
|
||||
$otherQueries[] = $cursorDirection === Database::CURSOR_AFTER ? Query::cursorAfter($cursorDocument) : Query::cursorBefore($cursorDocument);
|
||||
}
|
||||
|
||||
$allQueries = \array_merge($filterQueries, $otherQueries);
|
||||
|
||||
if (!empty($allQueries)) {
|
||||
$attributes = $collection->getAttribute('attributes', []);
|
||||
$validator = new QueriesValidator(new QueryValidator($attributes), $attributes, $collection->getAttribute('indexes', []), true);
|
||||
if (!$validator->isValid($allQueries)) {
|
||||
throw new Exception($validator->getDescription(), 400, Exception::GENERAL_QUERY_INVALID);
|
||||
}
|
||||
}
|
||||
|
||||
if ($documentSecurity) {
|
||||
$documents = $dbForProject->find('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $queries, $limit, $offset, $orderAttributes, $orderTypes, $cursorDocument ?? null, $cursorDirection);
|
||||
$total = $dbForProject->count('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $queries, APP_LIMIT_COUNT);
|
||||
$documents = $dbForProject->find('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $allQueries);
|
||||
$total = $dbForProject->count('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $filterQueries, APP_LIMIT_COUNT);
|
||||
} else {
|
||||
$documents = Authorization::skip(fn() => $dbForProject->find('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $queries, $limit, $offset, $orderAttributes, $orderTypes, $cursorDocument ?? null, $cursorDirection));
|
||||
$total = Authorization::skip(fn() => $dbForProject->count('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $queries, APP_LIMIT_COUNT));
|
||||
$documents = Authorization::skip(fn () => $dbForProject->find('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $allQueries));
|
||||
$total = Authorization::skip(fn () => $dbForProject->count('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $filterQueries, APP_LIMIT_COUNT));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -2255,8 +2339,6 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum
|
|||
throw new Exception('Document not found', 404, Exception::DOCUMENT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$permissions = PermissionsProcessor::handleAggregates($permissions);
|
||||
|
||||
if ($documentSecurity) {
|
||||
$valid |= $validator->isValid($document->getUpdate());
|
||||
}
|
||||
|
|
@ -2264,11 +2346,19 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum
|
|||
throw new Exception('Unauthorized permissions', 401, Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
// 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)) {
|
||||
if (!\is_null($permissions)) {
|
||||
if (!PermissionsProcessor::allowedForUserType($permissions)) {
|
||||
throw new Exception('Permissions must be one of: (' . \implode(', ', Authorization::getRoles()) . ')', 400, Exception::USER_UNAUTHORIZED);
|
||||
|
||||
if (!Auth::isAppUser($roles) && !Auth::isPrivilegedUser($roles) && !\is_null($permissions)) {
|
||||
foreach (Database::PERMISSIONS as $type) {
|
||||
foreach ($permissions as $permission) {
|
||||
if (!\str_starts_with($permission, $type)) {
|
||||
continue;
|
||||
}
|
||||
$role = \str_replace([$type, '(', ')', '"', ' '], '', $permission);
|
||||
if (!Authorization::isRole($role)) {
|
||||
throw new Exception('Permissions must be one of: (' . \implode(', ', Authorization::getRoles()) . ')', 400, Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2477,9 +2567,11 @@ App::get('/v1/databases/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) {
|
||||
|
|
@ -2499,11 +2591,11 @@ App::get('/v1/databases/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--;
|
||||
}
|
||||
// TODO@kodumbeats explore performance if query is ordered by time ASC
|
||||
// Added 3'rd level to Index [period, metric, time] because of order by.
|
||||
$stats[$metric] = array_reverse($stats[$metric]);
|
||||
}
|
||||
});
|
||||
|
|
@ -2589,9 +2681,11 @@ App::get('/v1/databases/:databaseId/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) {
|
||||
|
|
@ -2611,7 +2705,7 @@ App::get('/v1/databases/:databaseId/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--;
|
||||
}
|
||||
|
|
@ -2702,9 +2796,11 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/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) {
|
||||
|
|
@ -2724,7 +2820,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/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--;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ 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;
|
||||
|
|
@ -24,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;
|
||||
|
|
@ -65,7 +67,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,
|
||||
|
|
@ -76,8 +78,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]),
|
||||
]));
|
||||
|
|
@ -104,28 +106,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("Function '{$cursor}' for the 'cursor' value not found.", 400, Exception::GENERAL_CURSOR_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("Function '{$cursor}' for the 'cursor' value not found.", 400, Exception::GENERAL_CURSOR_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);
|
||||
});
|
||||
|
||||
|
|
@ -237,9 +245,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) {
|
||||
|
|
@ -259,7 +269,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--;
|
||||
}
|
||||
|
|
@ -311,8 +321,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,
|
||||
|
|
@ -320,7 +330,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')]),
|
||||
])));
|
||||
|
|
@ -332,9 +342,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());
|
||||
|
|
@ -384,11 +393,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
|
||||
|
|
@ -396,8 +405,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
|
||||
|
|
@ -497,7 +506,7 @@ App::post('/v1/functions/:functionId/deployments')
|
|||
}
|
||||
|
||||
$contentRange = $request->getHeader('content-range');
|
||||
$deploymentId = $dbForProject->getId();
|
||||
$deploymentId = ID::unique();
|
||||
$chunk = 1;
|
||||
$chunks = 1;
|
||||
|
||||
|
|
@ -555,9 +564,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) {
|
||||
|
|
@ -651,7 +660,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) {
|
||||
|
|
@ -662,25 +671,31 @@ App::get('/v1/functions/:functionId/deployments')
|
|||
throw new Exception('Function not found', 404, Exception::FUNCTION_NOT_FOUND);
|
||||
}
|
||||
|
||||
if (!empty($cursor)) {
|
||||
$cursorDeployment = $dbForProject->getDocument('deployments', $cursor);
|
||||
|
||||
if ($cursorDeployment->isEmpty()) {
|
||||
throw new Exception("Tag '{$cursor}' for the 'cursor' value not found.", 400, Exception::GENERAL_CURSOR_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("Tag '{$cursor}' for the 'cursor' value not found.", 400, Exception::GENERAL_CURSOR_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', ''));
|
||||
|
|
@ -857,7 +872,7 @@ App::post('/v1/functions/:functionId/executions')
|
|||
throw new Exception($validator->getDescription(), 401, Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
$executionId = $dbForProject->getId();
|
||||
$executionId = ID::unique();
|
||||
|
||||
/** @var Document $execution */
|
||||
$execution = Authorization::skip(fn () => $dbForProject->createDocument('executions', new Document([
|
||||
|
|
@ -934,7 +949,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(),
|
||||
|
|
@ -955,12 +969,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());
|
||||
}
|
||||
|
||||
|
|
@ -1004,24 +1018,30 @@ App::get('/v1/functions/:functionId/executions')
|
|||
throw new Exception('Function not found', 404, Exception::FUNCTION_NOT_FOUND);
|
||||
}
|
||||
|
||||
if (!empty($cursor)) {
|
||||
$cursorExecution = $dbForProject->getDocument('executions', $cursor);
|
||||
|
||||
if ($cursorExecution->isEmpty()) {
|
||||
throw new Exception("Execution '{$cursor}' for the 'cursor' value not found.", 400, Exception::GENERAL_CURSOR_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("Execution '{$cursor}' for the 'cursor' value not found.", 400, Exception::GENERAL_CURSOR_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);
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'executions' => $results,
|
||||
|
|
|
|||
|
|
@ -17,10 +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;
|
||||
|
|
@ -28,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;
|
||||
|
|
@ -81,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("'console' is a reserved project.", 400, Exception::PROJECT_RESERVED_PROJECT);
|
||||
|
|
@ -90,11 +92,11 @@ App::post('/v1/projects')
|
|||
$project = $dbForConsole->createDocument('projects', new Document([
|
||||
'$id' => $projectId,
|
||||
'$permissions' => [
|
||||
Permission::read(Role::team($teamId)),
|
||||
Permission::update(Role::team($teamId, 'owner')),
|
||||
Permission::update(Role::team($teamId, 'developer')),
|
||||
Permission::delete(Role::team($teamId, 'owner')),
|
||||
Permission::delete(Role::team($teamId, 'developer')),
|
||||
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(),
|
||||
|
|
@ -108,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' => [],
|
||||
|
|
@ -183,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("Project '{$cursor}' for the 'cursor' value not found.", 400, Exception::GENERAL_CURSOR_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("Project '{$cursor}' for the 'cursor' value not found.", 400, Exception::GENERAL_CURSOR_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,
|
||||
|
|
@ -300,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) {
|
||||
|
|
@ -322,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--;
|
||||
}
|
||||
|
|
@ -596,7 +606,7 @@ App::post('/v1/projects/:projectId/webhooks')
|
|||
$security = (bool) filter_var($security, FILTER_VALIDATE_BOOLEAN);
|
||||
|
||||
$webhook = new Document([
|
||||
'$id' => $dbForConsole->getId(),
|
||||
'$id' => ID::unique(),
|
||||
'$permissions' => [
|
||||
Permission::read(Role::any()),
|
||||
Permission::update(Role::any()),
|
||||
|
|
@ -643,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,
|
||||
|
|
@ -675,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()) {
|
||||
|
|
@ -717,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()) {
|
||||
|
|
@ -763,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()) {
|
||||
|
|
@ -801,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()) {
|
||||
|
|
@ -831,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);
|
||||
|
||||
|
|
@ -843,7 +854,7 @@ App::post('/v1/projects/:projectId/keys')
|
|||
}
|
||||
|
||||
$key = new Document([
|
||||
'$id' => $dbForConsole->getId(),
|
||||
'$id' => ID::unique(),
|
||||
'$permissions' => [
|
||||
Permission::read(Role::any()),
|
||||
Permission::update(Role::any()),
|
||||
|
|
@ -887,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,
|
||||
|
|
@ -919,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()) {
|
||||
|
|
@ -944,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);
|
||||
|
||||
|
|
@ -956,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()) {
|
||||
|
|
@ -999,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()) {
|
||||
|
|
@ -1042,11 +1054,11 @@ App::post('/v1/projects/:projectId/platforms')
|
|||
}
|
||||
|
||||
$platform = new Document([
|
||||
'$id' => $dbForConsole->getId(),
|
||||
'$id' => ID::unique(),
|
||||
'$permissions' => [
|
||||
'read(any)',
|
||||
'update(any)',
|
||||
'delete(any)',
|
||||
Permission::read(Role::any()),
|
||||
Permission::update(Role::any()),
|
||||
Permission::delete(Role::any()),
|
||||
],
|
||||
'projectInternalId' => $project->getInternalId(),
|
||||
'projectId' => $project->getId(),
|
||||
|
|
@ -1087,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,
|
||||
|
|
@ -1119,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()) {
|
||||
|
|
@ -1156,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()) {
|
||||
|
|
@ -1200,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()) {
|
||||
|
|
@ -1240,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()) {
|
||||
|
|
@ -1257,15 +1270,15 @@ App::post('/v1/projects/:projectId/domains')
|
|||
$domain = new Domain($domain);
|
||||
|
||||
$domain = new Document([
|
||||
'$id' => $dbForConsole->getId(),
|
||||
'$id' => ID::unique(),
|
||||
'$permissions' => [
|
||||
'read(any)',
|
||||
'update(any)',
|
||||
'delete(any)',
|
||||
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(),
|
||||
|
|
@ -1303,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,
|
||||
|
|
@ -1335,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()) {
|
||||
|
|
@ -1369,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()) {
|
||||
|
|
@ -1429,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()) {
|
||||
|
|
|
|||
|
|
@ -16,9 +16,12 @@ use Utopia\Cache\Cache;
|
|||
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\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\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Permissions;
|
||||
|
|
@ -70,7 +73,7 @@ App::post('/v1/storage/buckets')
|
|||
->inject('events')
|
||||
->action(function (string $bucketId, string $name, ?array $permissions, string $fileSecurity, bool $enabled, int $maximumFileSize, array $allowedFileExtensions, bool $encryption, bool $antivirus, Response $response, Database $dbForProject, Audit $audits, Stats $usage, Event $events) {
|
||||
|
||||
$bucketId = $bucketId === 'unique()' ? $dbForProject->getId() : $bucketId;
|
||||
$bucketId = $bucketId === 'unique()' ? ID::unique() : $bucketId;
|
||||
try {
|
||||
$files = Config::getParam('collections', [])['files'] ?? [];
|
||||
if (empty($files)) {
|
||||
|
|
@ -156,27 +159,37 @@ 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')
|
||||
->inject('usage')
|
||||
->action(function (string $search, int $limit, int $offset, string $cursor, string $cursorDirection, string $orderType, Response $response, Database $dbForProject, Stats $usage) {
|
||||
|
||||
$queries = ($search) ? [new Query('name', Query::TYPE_SEARCH, [$search])] : [];
|
||||
$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("Bucket '{$cursor}' for the 'cursor' value not found.", 400, Exception::GENERAL_CURSOR_NOT_FOUND);
|
||||
}
|
||||
|
||||
$queries[] = $cursorDirection === Database::CURSOR_AFTER ? Query::cursorAfter($cursorDocument) : Query::cursorBefore($cursorDocument);
|
||||
}
|
||||
|
||||
$usage->setParam('storage.buckets.read', 1);
|
||||
|
||||
$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);
|
||||
});
|
||||
|
||||
|
|
@ -338,7 +351,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('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of strings with permissions. By default no user is granted with any 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, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE]), 'An array of strings with permissions. By default no user is granted with any permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.', true)
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
|
|
@ -362,22 +375,44 @@ App::post('/v1/storage/buckets/:bucketId/files')
|
|||
throw new Exception('Unauthorized permissions', 401, Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
$permissions = PermissionsProcessor::addDefaultsIfNeeded(
|
||||
$permissions,
|
||||
$user->getId(),
|
||||
allowedPermissions: \array_filter(
|
||||
Database::PERMISSIONS,
|
||||
fn ($permission) => $permission !== Database::PERMISSION_CREATE
|
||||
),
|
||||
/**
|
||||
* Add permissions for current the user for any missing types
|
||||
* from the allowed permissions for this resource type.
|
||||
*/
|
||||
$allowedPermissions = \array_filter(
|
||||
Database::PERMISSIONS,
|
||||
fn ($permission) => $permission !== Database::PERMISSION_CREATE
|
||||
);
|
||||
|
||||
$permissions = PermissionsProcessor::handleAggregates($permissions);
|
||||
|
||||
if (!PermissionsProcessor::allowedForResourceType('file', $permissions)) {
|
||||
throw new Exception('Invalid permission', 400, Exception::GENERAL_PERMISSION_INVALID);
|
||||
if (\is_null($permissions)) {
|
||||
$permissions = [];
|
||||
if (!empty($user->getId())) {
|
||||
foreach ($allowedPermissions as $permission) {
|
||||
$permissions[] = (new Permission($permission, 'user', $user->getId()))->toString();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
foreach ($allowedPermissions as $permission) {
|
||||
// Default any missing allowed permissions to the current user
|
||||
if (empty(\preg_grep("#^{$permission}\(.+\)$#", $permissions)) && !empty($user->getId())) {
|
||||
$permissions[] = (new Permission($permission, 'user', $user->getId()))->toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!PermissionsProcessor::allowedForUserType($permissions)) {
|
||||
throw new Exception('Permissions must be one of: (' . \implode(', ', Authorization::getRoles()) . ')', 400, Exception::USER_UNAUTHORIZED);
|
||||
|
||||
// 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 (Database::PERMISSIONS as $type) {
|
||||
foreach ($permissions as $permission) {
|
||||
if (!\str_starts_with($permission, $type)) {
|
||||
continue;
|
||||
}
|
||||
$role = \str_replace([$type, '(', ')', '"', ' '], '', $permission);
|
||||
if (!Authorization::isRole($role)) {
|
||||
throw new Exception('Permissions must be one of: (' . \implode(', ', Authorization::getRoles()) . ')', 400, Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$file = $request->getFiles('file');
|
||||
|
|
@ -404,7 +439,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;
|
||||
|
||||
|
|
@ -453,9 +488,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
|
||||
|
||||
$file = Authorization::skip(function () use ($dbForProject, $bucket, $fileId) {
|
||||
return $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
|
||||
});
|
||||
$file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
|
||||
|
||||
$metadata = ['content_type' => $deviceLocal->getFileMimeType($fileTmpName)];
|
||||
if (!$file->isEmpty()) {
|
||||
|
|
@ -544,7 +577,7 @@ App::post('/v1/storage/buckets/:bucketId/files')
|
|||
'metadata' => $metadata,
|
||||
]);
|
||||
|
||||
$file = Authorization::skip(fn () => $dbForProject->createDocument('bucket_' . $bucket->getInternalId(), $doc));
|
||||
$file = $dbForProject->createDocument('bucket_' . $bucket->getInternalId(), $doc);
|
||||
} else {
|
||||
$file = $file
|
||||
->setAttribute('$permissions', $permissions)
|
||||
|
|
@ -559,7 +592,7 @@ App::post('/v1/storage/buckets/:bucketId/files')
|
|||
->setAttribute('metadata', $metadata)
|
||||
->setAttribute('chunksUploaded', $chunksUploaded);
|
||||
|
||||
$file = Authorization::skip(fn () => $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file));
|
||||
$file = $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file);
|
||||
}
|
||||
} catch (StructureException $exception) {
|
||||
throw new Exception($exception->getMessage(), 400, Exception::DOCUMENT_INVALID_STRUCTURE);
|
||||
|
|
@ -580,7 +613,7 @@ App::post('/v1/storage/buckets/:bucketId/files')
|
|||
try {
|
||||
if ($file->isEmpty()) {
|
||||
$doc = new Document([
|
||||
'$id' => $fileId,
|
||||
'$id' => ID::custom($fileId),
|
||||
'$permissions' => $permissions,
|
||||
'bucketId' => $bucket->getId(),
|
||||
'name' => $fileName,
|
||||
|
|
@ -597,13 +630,13 @@ App::post('/v1/storage/buckets/:bucketId/files')
|
|||
'metadata' => $metadata,
|
||||
]);
|
||||
|
||||
$file = Authorization::skip(fn () => $dbForProject->createDocument('bucket_' . $bucket->getInternalId(), $doc));
|
||||
$file = $dbForProject->createDocument('bucket_' . $bucket->getInternalId(), $doc);
|
||||
} else {
|
||||
$file = $file
|
||||
->setAttribute('chunksUploaded', $chunksUploaded)
|
||||
->setAttribute('metadata', $metadata);
|
||||
|
||||
$file = Authorization::skip(fn () => $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file));
|
||||
$file = $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file);
|
||||
}
|
||||
} catch (StructureException $exception) {
|
||||
throw new Exception($exception->getMessage(), 400, Exception::DOCUMENT_INVALID_STRUCTURE);
|
||||
|
|
@ -642,7 +675,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('usage')
|
||||
|
|
@ -660,34 +693,32 @@ App::get('/v1/storage/buckets/:bucketId/files')
|
|||
throw new Exception('Unauthorized permissions', 401, 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('fileSecurity', false)) {
|
||||
$cursorFile = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $cursor);
|
||||
} else {
|
||||
$cursorFile = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $cursor));
|
||||
}
|
||||
|
||||
if ($cursorFile->isEmpty()) {
|
||||
throw new Exception("File '{$cursor}' for the 'cursor' value not found.", 400, Exception::GENERAL_CURSOR_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)) {
|
||||
$cursorDocument = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $cursor);
|
||||
|
||||
if (!empty($search)) {
|
||||
$queries[] = new Query('search', Query::TYPE_SEARCH, [$search]);
|
||||
if ($cursorDocument->isEmpty()) {
|
||||
throw new Exception("File '{$cursor}' for the 'cursor' value not found.", 400, Exception::GENERAL_CURSOR_NOT_FOUND);
|
||||
}
|
||||
|
||||
$queries[] = $cursorDirection === Database::CURSOR_AFTER ? Query::cursorAfter($cursorDocument) : Query::cursorBefore($cursorDocument);
|
||||
}
|
||||
|
||||
if ($bucket->getAttribute('fileSecurity', false)) {
|
||||
$files = $dbForProject->find('bucket_' . $bucket->getInternalId(), $queries, $limit, $offset, [], [$orderType], $cursorFile ?? null, $cursorDirection);
|
||||
$files = $dbForProject->find('bucket_' . $bucket->getInternalId(), \array_merge($filterQueries, $queries));
|
||||
$total = $dbForProject->count('bucket_' . $bucket->getInternalId(), $filterQueries, APP_LIMIT_COUNT);
|
||||
} else {
|
||||
$files = Authorization::skip(fn () => $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));
|
||||
}
|
||||
|
||||
$usage
|
||||
|
|
@ -697,7 +728,7 @@ App::get('/v1/storage/buckets/:bucketId/files')
|
|||
|
||||
$response->dynamic(new Document([
|
||||
'files' => $files,
|
||||
'total' => $dbForProject->count('bucket_' . $bucket->getInternalId(), $queries, APP_LIMIT_COUNT),
|
||||
'total' => $total,
|
||||
]), Response::MODEL_FILE_LIST);
|
||||
});
|
||||
|
||||
|
|
@ -1276,10 +1307,20 @@ App::put('/v1/storage/buckets/:bucketId/files/:fileId')
|
|||
throw new Exception('Unauthorized permissions', 401, Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
$permissions = PermissionsProcessor::handleAggregates($permissions);
|
||||
|
||||
if (!PermissionsProcessor::allowedForUserType($permissions)) {
|
||||
throw new Exception('Permissions must be one of: (' . \implode(', ', Authorization::getRoles()) . ')', 400, Exception::USER_UNAUTHORIZED);
|
||||
// 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 (Database::PERMISSIONS as $type) {
|
||||
foreach ($permissions as $permission) {
|
||||
if (!\str_starts_with($permission, $type)) {
|
||||
continue;
|
||||
}
|
||||
$role = \str_replace([$type, '(', ')', '"', ' '], '', $permission);
|
||||
if (!Authorization::isRole($role)) {
|
||||
throw new Exception('Permissions must be one of: (' . \implode(', ', Authorization::getRoles()) . ')', 400, Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$file->setAttribute('$permissions', $permissions);
|
||||
|
|
@ -1453,9 +1494,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) {
|
||||
|
|
@ -1475,7 +1518,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--;
|
||||
}
|
||||
|
|
@ -1562,9 +1605,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) {
|
||||
|
|
@ -1584,7 +1629,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--;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,13 +16,16 @@ 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;
|
||||
|
|
@ -58,13 +61,13 @@ 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 ,
|
||||
'$id' => $teamId,
|
||||
'$permissions' => [
|
||||
Permission::read(Role::team($teamId)),
|
||||
Permission::update(Role::team($teamId, 'owner')),
|
||||
Permission::delete(Role::team($teamId, 'owner')),
|
||||
Permission::update(Role::team($teamId), 'owner'),
|
||||
Permission::delete(Role::team($teamId), 'owner'),
|
||||
],
|
||||
'name' => $name,
|
||||
'total' => ($isPrivilegedUser || $isAppUser) ? 0 : 1,
|
||||
|
|
@ -72,7 +75,7 @@ App::post('/v1/teams')
|
|||
])));
|
||||
|
||||
if (!$isPrivilegedUser && !$isAppUser) { // Don't add user on server mode
|
||||
$membershipId = $dbForProject->getId();
|
||||
$membershipId = ID::unique();
|
||||
$membership = new Document([
|
||||
'$id' => $membershipId,
|
||||
'$permissions' => [
|
||||
|
|
@ -88,8 +91,8 @@ App::post('/v1/teams')
|
|||
'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()])
|
||||
|
|
@ -136,22 +139,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("Team '{$cursor}' for the 'cursor' value not found.", 400, Exception::GENERAL_CURSOR_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("Team '{$cursor}' for the 'cursor' value not found.", 400, Exception::GENERAL_CURSOR_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,
|
||||
|
|
@ -246,8 +255,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) {
|
||||
|
|
@ -322,7 +332,7 @@ App::post('/v1/teams/:teamId/memberships')
|
|||
throw new Exception('Team not found', 404, 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;
|
||||
|
|
@ -336,7 +346,7 @@ App::post('/v1/teams/:teamId/memberships')
|
|||
}
|
||||
|
||||
try {
|
||||
$userId = $dbForProject->getId();
|
||||
$userId = ID::unique();
|
||||
$invitee = Authorization::skip(fn() => $dbForProject->createDocument('users', new Document([
|
||||
'$id' => $userId,
|
||||
'$permissions' => [
|
||||
|
|
@ -354,8 +364,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(),
|
||||
|
|
@ -377,7 +387,7 @@ App::post('/v1/teams/:teamId/memberships')
|
|||
|
||||
$secret = Auth::tokenGenerator();
|
||||
|
||||
$membershipId = $dbForProject->getId();
|
||||
$membershipId = ID::unique();
|
||||
$membership = new Document([
|
||||
'$id' => $membershipId,
|
||||
'$permissions' => [
|
||||
|
|
@ -392,8 +402,8 @@ App::post('/v1/teams/:teamId/memberships')
|
|||
'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()])
|
||||
|
|
@ -470,7 +480,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) {
|
||||
|
|
@ -481,33 +491,34 @@ App::get('/v1/teams/:teamId/memberships')
|
|||
throw new Exception('Team not found', 404, Exception::TEAM_NOT_FOUND);
|
||||
}
|
||||
|
||||
if (!empty($cursor)) {
|
||||
$cursorMembership = $dbForProject->getDocument('memberships', $cursor);
|
||||
|
||||
if ($cursorMembership->isEmpty()) {
|
||||
throw new Exception("Membership '{$cursor}' for the 'cursor' value not found.", 400, Exception::GENERAL_CURSOR_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("Membership '{$cursor}' for the 'cursor' value not found.", 400, Exception::GENERAL_CURSOR_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
|
||||
);
|
||||
|
||||
|
|
@ -706,7 +717,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
|||
}
|
||||
|
||||
$membership // Attach user to team
|
||||
->setAttribute('joined', \time())
|
||||
->setAttribute('joined', DateTime::now())
|
||||
->setAttribute('confirm', true)
|
||||
;
|
||||
|
||||
|
|
@ -720,16 +731,16 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
|||
|
||||
$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']) : '--',
|
||||
|
|
@ -766,8 +777,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(
|
||||
|
|
@ -820,6 +831,14 @@ App::delete('/v1/teams/:teamId/memberships/:membershipId')
|
|||
throw new Exception('Team not found', 404, Exception::TEAM_NOT_FOUND);
|
||||
}
|
||||
|
||||
/**
|
||||
* Force document security
|
||||
*/
|
||||
$validator = new Authorization('delete');
|
||||
if (!$validator->isValid($membership->getDelete())) {
|
||||
throw new Exception('Unauthorized permissions', 401, Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
try {
|
||||
$dbForProject->deleteDocument('memberships', $membership->getId());
|
||||
} catch (AuthorizationException $exception) {
|
||||
|
|
|
|||
|
|
@ -14,9 +14,13 @@ 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;
|
||||
|
|
@ -54,7 +58,7 @@ App::post('/v1/users')
|
|||
$email = \strtolower($email);
|
||||
|
||||
try {
|
||||
$userId = $userId == 'unique()' ? $dbForProject->getId() : $userId;
|
||||
$userId = $userId == 'unique()' ? ID::unique() : $userId;
|
||||
$user = $dbForProject->createDocument('users', new Document([
|
||||
'$id' => $userId,
|
||||
'$permissions' => [
|
||||
|
|
@ -66,8 +70,8 @@ App::post('/v1/users')
|
|||
'emailVerification' => false,
|
||||
'status' => true,
|
||||
'password' => Auth::passwordHash($password),
|
||||
'passwordUpdate' => \time(),
|
||||
'registration' => \time(),
|
||||
'passwordUpdate' => DateTime::now(),
|
||||
'registration' => DateTime::now(),
|
||||
'reset' => false,
|
||||
'name' => $name,
|
||||
'prefs' => new \stdClass(),
|
||||
|
|
@ -108,24 +112,30 @@ App::get('/v1/users')
|
|||
->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)
|
||||
->param('orderType', Database::ORDER_ASC, new WhiteList([Database::ORDER_ASC, Database::ORDER_DESC], true), 'Order result by ASC or DESC order.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('usage')
|
||||
->action(function (string $search, int $limit, int $offset, string $cursor, string $cursorDirection, string $orderType, Response $response, Database $dbForProject, Stats $usage) {
|
||||
|
||||
if (!empty($cursor)) {
|
||||
$cursorUser = $dbForProject->getDocument('users', $cursor);
|
||||
$filterQueries = [];
|
||||
|
||||
if ($cursorUser->isEmpty()) {
|
||||
throw new Exception("User '{$cursor}' for the 'cursor' value not found.", 400, Exception::GENERAL_CURSOR_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('users', $cursor);
|
||||
|
||||
if (!empty($search)) {
|
||||
$queries[] = new Query('search', Query::TYPE_SEARCH, [$search]);
|
||||
if ($cursorDocument->isEmpty()) {
|
||||
throw new Exception("User '{$cursor}' for the 'cursor' value not found.", 400, Exception::GENERAL_CURSOR_NOT_FOUND);
|
||||
}
|
||||
|
||||
$queries[] = $cursorDirection === Database::CURSOR_AFTER ? Query::cursorAfter($cursorDocument) : Query::cursorBefore($cursorDocument);
|
||||
}
|
||||
|
||||
$usage
|
||||
|
|
@ -133,8 +143,8 @@ App::get('/v1/users')
|
|||
;
|
||||
|
||||
$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', \array_merge($filterQueries, $queries)),
|
||||
'total' => $dbForProject->count('users', $filterQueries, APP_LIMIT_COUNT),
|
||||
]), Response::MODEL_USER_LIST);
|
||||
});
|
||||
|
||||
|
|
@ -553,7 +563,7 @@ App::patch('/v1/users/:userId/password')
|
|||
|
||||
$user
|
||||
->setAttribute('password', Auth::passwordHash($password))
|
||||
->setAttribute('passwordUpdate', \time());
|
||||
->setAttribute('passwordUpdate', DateTime::now());
|
||||
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
|
||||
|
||||
|
|
@ -907,9 +917,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) {
|
||||
|
|
@ -929,7 +941,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--;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,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 +90,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 +98,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) {
|
||||
|
|
@ -289,16 +290,16 @@ 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);
|
||||
$expire = $key->getAttribute('expire');
|
||||
|
||||
if (!empty($expire) && $expire < \time()) {
|
||||
if (!empty($expire) && $expire < DateTime::now()) {
|
||||
throw new AppwriteException('Project key expired', 401, AppwriteException:: PROJECT_KEY_EXPIRED);
|
||||
}
|
||||
|
||||
Authorization::setRole(Auth::USER_ROLE_APP);
|
||||
Authorization::setRole(Auth::USER_ROLE_APPS);
|
||||
Authorization::setDefaultStatus(false); // Cancel security segmentation for API keys.
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -42,9 +42,9 @@ App::init()
|
|||
throw new Exception('Missing or unknown project ID', 400, Exception::PROJECT_UNKNOWN);
|
||||
}
|
||||
|
||||
/*
|
||||
* Abuse Check
|
||||
*/
|
||||
/*
|
||||
* Abuse Check
|
||||
*/
|
||||
$abuseKeyLabel = $route->getLabel('abuse-key', 'url:{url},ip:{ip}');
|
||||
$timeLimitArray = [];
|
||||
|
||||
|
|
@ -53,10 +53,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;
|
||||
}
|
||||
|
||||
|
|
@ -74,13 +74,16 @@ 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)
|
||||
;
|
||||
}
|
||||
|
||||
|
|
@ -93,13 +96,13 @@ App::init()
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Background Jobs
|
||||
*/
|
||||
/*
|
||||
* Background Jobs
|
||||
*/
|
||||
$events
|
||||
->setEvent($route->getLabel('event', ''))
|
||||
->setProject($project)
|
||||
->setUser($user)
|
||||
->setEvent($route->getLabel('event', ''))
|
||||
->setProject($project)
|
||||
->setUser($user)
|
||||
;
|
||||
|
||||
$mails
|
||||
|
|
@ -181,7 +184,6 @@ App::init()
|
|||
|
||||
default:
|
||||
throw new Exception('Unsupported authentication route', 501, Exception::USER_AUTH_METHOD_UNSUPPORTED);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
13
app/http.php
13
app/http.php
|
|
@ -10,6 +10,7 @@ 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;
|
||||
|
|
@ -134,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'],
|
||||
|
|
@ -148,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'],
|
||||
|
|
@ -162,8 +163,8 @@ $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',
|
||||
'maximumFileSize' => (int) App::getEnv('_APP_STORAGE_LIMIT', 0), // 10MB
|
||||
'allowedFileExtensions' => [],
|
||||
|
|
@ -192,7 +193,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'],
|
||||
|
|
@ -206,7 +207,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'],
|
||||
|
|
|
|||
74
app/init.php
74
app/init.php
|
|
@ -43,6 +43,7 @@ use Appwrite\OpenSSL\OpenSSL;
|
|||
use Appwrite\Stats\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';
|
||||
|
|
@ -267,9 +270,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()),
|
||||
]);
|
||||
}
|
||||
);
|
||||
|
||||
|
|
@ -281,9 +285,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),
|
||||
]);
|
||||
}
|
||||
);
|
||||
|
||||
|
|
@ -295,8 +300,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),
|
||||
]);
|
||||
}
|
||||
);
|
||||
|
||||
|
|
@ -308,8 +314,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),
|
||||
]);
|
||||
}
|
||||
);
|
||||
|
||||
|
|
@ -321,8 +328,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),
|
||||
]);
|
||||
}
|
||||
);
|
||||
|
||||
|
|
@ -334,8 +342,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),
|
||||
]);
|
||||
}
|
||||
);
|
||||
|
||||
|
|
@ -346,8 +355,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),
|
||||
]));
|
||||
}
|
||||
);
|
||||
|
||||
|
|
@ -359,8 +369,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),
|
||||
]));
|
||||
}
|
||||
);
|
||||
|
||||
|
|
@ -372,8 +383,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),
|
||||
]));
|
||||
}
|
||||
);
|
||||
|
||||
|
|
@ -410,6 +422,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);
|
||||
|
|
@ -711,7 +727,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(),
|
||||
|
|
@ -787,7 +803,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);
|
||||
}
|
||||
|
|
@ -799,14 +815,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']);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -829,7 +845,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']);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -854,10 +870,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,
|
||||
|
|
@ -865,7 +881,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,10 @@ use Utopia\Abuse\Abuse;
|
|||
use Utopia\Abuse\Adapters\TimeLimit;
|
||||
use Utopia\App;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Database\ID;
|
||||
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;
|
||||
|
|
@ -146,11 +147,11 @@ $server->onStart(function () use ($stats, $register, $containerId, &$statsDocume
|
|||
try {
|
||||
$attempts++;
|
||||
$document = new Document([
|
||||
'$id' => $database->getId(),
|
||||
'$collection' => 'realtime',
|
||||
'$id' => ID::unique(),
|
||||
'$collection' => ID::custom('realtime'),
|
||||
'$permissions' => [],
|
||||
'container' => $containerId,
|
||||
'timestamp' => time(),
|
||||
'timestamp' => DateTime::now(),
|
||||
'value' => '{}'
|
||||
]);
|
||||
|
||||
|
|
@ -180,7 +181,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));
|
||||
|
|
@ -208,7 +209,7 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
|
|||
$payload = [];
|
||||
|
||||
$list = Authorization::skip(fn () => $database->find('realtime', [
|
||||
new Query('timestamp', Query::TYPE_GREATER, [(time() - 15)])
|
||||
Query::greaterThan('timestamp', DateTime::addSeconds(new \DateTime(), -15)),
|
||||
]));
|
||||
|
||||
/**
|
||||
|
|
@ -235,7 +236,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]
|
||||
]
|
||||
|
|
@ -262,7 +263,7 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
|
|||
'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)
|
||||
|
||||
|
||||
|
|
@ -138,7 +140,8 @@ $cli
|
|||
Console::loop(function () use ($interval, $executionLogsRetention, $abuseLogsRetention, $auditLogRetention, $usageStatsRetention30m, $usageStatsRetention1d) {
|
||||
$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;
|
||||
|
|
|
|||
|
|
@ -318,6 +318,9 @@ $permissions = $this->getParam('permissions', 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>
|
||||
|
|
@ -589,8 +592,8 @@ $permissions = $this->getParam('permissions', 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
|
||||
|
|
@ -678,6 +681,60 @@ $permissions = $this->getParam('permissions', 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>
|
||||
|
||||
|
|
|
|||
|
|
@ -291,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
|
||||
|
|
|
|||
|
|
@ -143,6 +143,16 @@ $permissions = $this->getParam('permissions', 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
|
||||
|
|
@ -243,6 +253,16 @@ $permissions = $this->getParam('permissions', 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
|
||||
|
|
@ -361,8 +381,8 @@ $permissions = $this->getParam('permissions', 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"
|
||||
|
|
@ -619,8 +619,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"
|
||||
|
|
|
|||
|
|
@ -199,7 +199,7 @@ $fileUpdatePermissions = $this->getParam('fileUpdatePermissions', null);
|
|||
<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>
|
||||
|
|
@ -220,7 +220,7 @@ $fileUpdatePermissions = $this->getParam('fileUpdatePermissions', null);
|
|||
<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">
|
||||
|
|
@ -477,8 +477,8 @@ $fileUpdatePermissions = $this->getParam('fileUpdatePermissions', 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-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"
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -6,9 +6,11 @@ use Appwrite\Resque\Worker;
|
|||
use Appwrite\Utopia\Response\Model\Deployment;
|
||||
use Cron\CronExpression;
|
||||
use Executor\Executor;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
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,10 +77,9 @@ 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,
|
||||
'$permissions' => [],
|
||||
|
|
@ -91,7 +92,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);
|
||||
|
|
@ -183,13 +184,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);
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ use Utopia\App;
|
|||
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;
|
||||
|
|
@ -70,17 +69,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());
|
||||
}
|
||||
|
|
@ -88,15 +87,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:
|
||||
|
|
@ -105,7 +104,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;
|
||||
default:
|
||||
Console::error('No delete operation for type: ' . $type);
|
||||
|
|
@ -150,33 +149,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);
|
||||
});
|
||||
}
|
||||
|
|
@ -191,7 +190,7 @@ class DeletesV1 extends Worker
|
|||
|
||||
// Delete Memberships
|
||||
$this->deleteByGroup('memberships', [
|
||||
new Query('teamId', Query::TYPE_EQUAL, [$teamId])
|
||||
Query::equal('teamId', [$teamId])
|
||||
], $this->getProjectDB($projectId));
|
||||
}
|
||||
|
||||
|
|
@ -223,14 +222,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
|
||||
|
|
@ -251,67 +250,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])
|
||||
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);
|
||||
}
|
||||
|
|
@ -319,17 +318,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);
|
||||
}
|
||||
|
|
@ -337,14 +338,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);
|
||||
}
|
||||
|
||||
|
|
@ -364,7 +366,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)) {
|
||||
|
|
@ -381,7 +383,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', ''));
|
||||
|
|
@ -396,7 +398,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);
|
||||
|
||||
/**
|
||||
|
|
@ -440,7 +442,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', ''));
|
||||
|
|
@ -499,7 +501,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++;
|
||||
|
||||
|
|
@ -538,7 +540,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);
|
||||
|
||||
|
|
@ -565,7 +567,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,7 +237,7 @@ 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,
|
||||
'$permissions' => $user->isEmpty() ? [] : [Permission::read(Role::user($user->getId()))],
|
||||
|
|
@ -295,10 +298,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());
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ services:
|
|||
- ./phpunit.xml:/usr/src/code/phpunit.xml
|
||||
- ./tests:/usr/src/code/tests
|
||||
- ./app:/usr/src/code/app
|
||||
# - ./vendor/utopia/database:/usr/src/code/vendor/utopia/database
|
||||
# - ./vendor/utopia-php/database:/usr/src/code/vendor/utopia-php/database
|
||||
- ./docs:/usr/src/code/docs
|
||||
- ./public:/usr/src/code/public
|
||||
- ./src:/usr/src/code/src
|
||||
|
|
@ -561,6 +561,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:
|
||||
|
|
|
|||
|
|
@ -6,14 +6,14 @@
|
|||
convertNoticesToExceptions="true"
|
||||
convertWarningsToExceptions="true"
|
||||
processIsolation="false"
|
||||
stopOnFailure="false"
|
||||
stopOnFailure="true"
|
||||
>
|
||||
<extensions>
|
||||
<extension class="Appwrite\Tests\TestHook" />
|
||||
</extensions>
|
||||
<testsuites>
|
||||
<testsuite name="unit">
|
||||
<directory>./tests/unit</directory>
|
||||
<directory>./tests/unit/</directory>
|
||||
</testsuite>
|
||||
<testsuite name="e2e">
|
||||
<file>./tests/e2e/Client.php</file>
|
||||
|
|
|
|||
127
public/dist/scripts/app-all.js
vendored
127
public/dist/scripts/app-all.js
vendored
|
|
@ -257,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"');}
|
||||
|
|
@ -3856,111 +3865,9 @@ 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,from,to,){if(value&&Array.isArray(value)&&to!=='array'){value=value.map(element=>cast(element,from,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':if(value&&value.constructor&&value.constructor===Array){break;}
|
||||
if(from==='csv'){if(value.length===0){value=[];}else{value=value.split(',');}}else{value=[value];}
|
||||
|
|
@ -3992,7 +3899,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";}
|
||||
|
|
@ -4047,8 +3954,8 @@ this.events.add(event);this.reset();},removeEvent(value){this.events.delete(valu
|
|||
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(`${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);if(permission[key]){if(!this.rawPermissions.includes(`${parsedKey}(${permission.role})`)){this.rawPermissions.push(`${parsedKey}(${permission.role})`);}}else{this.rawPermissions=this.rawPermissions.filter(p=>{return!p.includes(`${parsedKey}(${permission.role})`);});}});});},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(' ','');return{type,role};},parseInputPermission(key){if(key==='delete'){return'xdelete';}
|
||||
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;}
|
||||
|
|
@ -4096,9 +4003,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();}
|
||||
|
|
|
|||
9
public/dist/scripts/app-dep.js
vendored
9
public/dist/scripts/app-dep.js
vendored
|
|
@ -257,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"');}
|
||||
|
|
|
|||
118
public/dist/scripts/app.js
vendored
118
public/dist/scripts/app.js
vendored
|
|
@ -521,111 +521,9 @@ 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,from,to,){if(value&&Array.isArray(value)&&to!=='array'){value=value.map(element=>cast(element,from,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':if(value&&value.constructor&&value.constructor===Array){break;}
|
||||
if(from==='csv'){if(value.length===0){value=[];}else{value=value.split(',');}}else{value=[value];}
|
||||
|
|
@ -657,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";}
|
||||
|
|
@ -712,8 +610,8 @@ this.events.add(event);this.reset();},removeEvent(value){this.events.delete(valu
|
|||
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(`${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);if(permission[key]){if(!this.rawPermissions.includes(`${parsedKey}(${permission.role})`)){this.rawPermissions.push(`${parsedKey}(${permission.role})`);}}else{this.rawPermissions=this.rawPermissions.filter(p=>{return!p.includes(`${parsedKey}(${permission.role})`);});}});});},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(' ','');return{type,role};},parseInputPermission(key){if(key==='delete'){return'xdelete';}
|
||||
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;}
|
||||
|
|
@ -761,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();}
|
||||
|
|
|
|||
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
|
|
@ -1459,7 +1459,8 @@
|
|||
*
|
||||
* You can use this endpoint to show different country flags icons to your
|
||||
* users. The code argument receives the 2 letter country code. Use width,
|
||||
* height and quality arguments to change the output settings.
|
||||
* height and quality arguments to change the output settings. Country codes
|
||||
* follow the [ISO 3166-1](http://en.wikipedia.org/wiki/ISO_3166-1) standard.
|
||||
*
|
||||
* When one dimension is specified and the other is 0, the image is scaled
|
||||
* with preserved aspect ratio. If both dimensions are 0, the API provides an
|
||||
|
|
@ -2084,6 +2085,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
|
||||
*
|
||||
|
|
@ -4395,7 +4443,7 @@
|
|||
* @param {string} projectId
|
||||
* @param {string} name
|
||||
* @param {string[]} scopes
|
||||
* @param {number} expire
|
||||
* @param {string} expire
|
||||
* @throws {AppwriteException}
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
|
@ -4460,7 +4508,7 @@
|
|||
* @param {string} keyId
|
||||
* @param {string} name
|
||||
* @param {string[]} scopes
|
||||
* @param {number} expire
|
||||
* @param {string} expire
|
||||
* @throws {AppwriteException}
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@
|
|||
let [type, enabled] = entry;
|
||||
type = this.parseOutputPermission(type);
|
||||
if (enabled) {
|
||||
this.rawPermissions.push(`${type}(${role})`);
|
||||
this.rawPermissions.push(this.buildPermission(type, role));
|
||||
}
|
||||
});
|
||||
this.permissions.push({
|
||||
|
|
@ -61,13 +61,14 @@
|
|||
return;
|
||||
}
|
||||
const parsedKey = this.parseOutputPermission(key);
|
||||
const permissionString = this.buildPermission(parsedKey, permission.role);
|
||||
if (permission[key]) {
|
||||
if (!this.rawPermissions.includes(`${parsedKey}(${permission.role})`)) {
|
||||
this.rawPermissions.push(`${parsedKey}(${permission.role})`);
|
||||
if (!this.rawPermissions.includes(permissionString)) {
|
||||
this.rawPermissions.push(permissionString);
|
||||
}
|
||||
} else {
|
||||
this.rawPermissions = this.rawPermissions.filter(p => {
|
||||
return !p.includes(`${parsedKey}(${permission.role})`);
|
||||
return !p.includes(permissionString);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
@ -82,9 +83,15 @@
|
|||
parsePermission(permission) {
|
||||
let parts = permission.split('(');
|
||||
let type = parts[0];
|
||||
let role = parts[1].replace(')', '').replace(' ', '');
|
||||
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') {
|
||||
|
|
|
|||
|
|
@ -2,603 +2,20 @@
|
|||
"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('en-US', {
|
||||
timeZone: 'UTC',
|
||||
hourCycle: 'h24',
|
||||
...format
|
||||
}).format(new Date(datetime));
|
||||
}
|
||||
|
||||
return {
|
||||
format: format,
|
||||
strtotime: strtotime
|
||||
}
|
||||
}(), true);
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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"; }
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
namespace Appwrite\Auth;
|
||||
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
|
||||
class Auth
|
||||
|
|
@ -16,7 +17,7 @@ class Auth
|
|||
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';
|
||||
|
||||
/**
|
||||
|
|
@ -206,7 +207,7 @@ class Auth
|
|||
$token->isSet('expire') &&
|
||||
$token->getAttribute('type') == $type &&
|
||||
$token->getAttribute('secret') === self::hash($secret) &&
|
||||
$token->getAttribute('expire') >= \time()
|
||||
$token->getAttribute('expire') >= DateTime::now()
|
||||
) {
|
||||
return (string)$token->getId();
|
||||
}
|
||||
|
|
@ -225,7 +226,7 @@ class Auth
|
|||
$token->isSet('expire') &&
|
||||
$token->getAttribute('type') == Auth::TOKEN_TYPE_PHONE &&
|
||||
$token->getAttribute('secret') === $secret &&
|
||||
$token->getAttribute('expire') >= \time()
|
||||
$token->getAttribute('expire') >= DateTime::now()
|
||||
) {
|
||||
return (string) $token->getId();
|
||||
}
|
||||
|
|
@ -251,9 +252,9 @@ class Auth
|
|||
$session->isSet('expire') &&
|
||||
$session->isSet('provider') &&
|
||||
$session->getAttribute('secret') === self::hash($secret) &&
|
||||
$session->getAttribute('expire') >= \time()
|
||||
$session->getAttribute('expire') >= DateTime::now()
|
||||
) {
|
||||
return (string)$session->getId();
|
||||
return $session->getId();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -289,7 +290,7 @@ class Auth
|
|||
*/
|
||||
public static function isAppUser(array $roles): bool
|
||||
{
|
||||
if (in_array(self::USER_ROLE_APP, $roles)) {
|
||||
if (in_array(self::USER_ROLE_APPS, $roles)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,10 +8,10 @@ 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 $datetime = null;
|
||||
protected ?string $datetime1d = null;
|
||||
protected ?string $datetime30m = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
|
|
@ -42,41 +42,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;
|
||||
}
|
||||
|
||||
|
|
@ -115,9 +112,9 @@ class Delete extends Event
|
|||
'project' => $this->project,
|
||||
'type' => $this->type,
|
||||
'document' => $this->document,
|
||||
'timestamp' => $this->timestamp,
|
||||
'timestamp1d' => $this->timestamp1d,
|
||||
'timestamp30m' => $this->timestamp30m
|
||||
'datetime' => $this->datetime,
|
||||
'datetime1d' => $this->datetime1d,
|
||||
'datetime30m' => $this->datetime30m
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Appwrite\Messaging\Adapter;
|
||||
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Appwrite\Messaging\Adapter;
|
||||
use Utopia\App;
|
||||
|
|
@ -146,7 +147,7 @@ class Realtime extends Adapter
|
|||
'data' => [
|
||||
'events' => $events,
|
||||
'channels' => $channels,
|
||||
'timestamp' => time(),
|
||||
'timestamp' => DateTime::now(),
|
||||
'payload' => $payload
|
||||
]
|
||||
]));
|
||||
|
|
@ -300,7 +301,7 @@ class Realtime extends Adapter
|
|||
$channels[] = 'files';
|
||||
$channels[] = 'buckets.' . $payload->getAttribute('bucketId') . '.files';
|
||||
$channels[] = 'buckets.' . $payload->getAttribute('bucketId') . '.files.' . $payload->getId();
|
||||
$roles = $bucket->getAttribute('fileSecurity', false)
|
||||
$roles = $bucket->getAttribute('fileSecurity', false)
|
||||
? \array_merge($bucket->getRead(), $payload->getRead())
|
||||
: $bucket->getRead();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -1,87 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Permissions;
|
||||
|
||||
use Appwrite\Auth\Auth;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
|
||||
class PermissionsProcessor
|
||||
{
|
||||
public static function handleAggregates(?array $permissions): ?array
|
||||
{
|
||||
if (\is_null($permissions)) {
|
||||
return null;
|
||||
}
|
||||
$aggregates = [
|
||||
'admin' => Database::PERMISSIONS,
|
||||
];
|
||||
foreach ($permissions as $i => $permission) {
|
||||
foreach ($aggregates as $type => $subTypes) {
|
||||
if (!\str_starts_with($permission, $type)) {
|
||||
continue;
|
||||
}
|
||||
$permissionsContents = \str_replace([$type . '(', ')', ' '], '', $permission);
|
||||
foreach ($subTypes as $subType) {
|
||||
$permissions[] = $subType . '(' . $permissionsContents . ')';
|
||||
}
|
||||
unset($permissions[$i]);
|
||||
}
|
||||
}
|
||||
return $permissions;
|
||||
}
|
||||
|
||||
public static function addDefaultsIfNeeded(
|
||||
?array $permissions,
|
||||
string $userId,
|
||||
array $allowedPermissions = Database::PERMISSIONS
|
||||
): array
|
||||
{
|
||||
if (\is_null($permissions)) {
|
||||
$permissions = [];
|
||||
if (!empty($userId)) {
|
||||
foreach ($allowedPermissions as $permission) {
|
||||
$permissions[] = $permission . '(user:' . $userId . ')';
|
||||
}
|
||||
}
|
||||
return $permissions;
|
||||
}
|
||||
foreach ($allowedPermissions as $permission) {
|
||||
// Default any missing allowed permissions to the current user
|
||||
if (empty(\preg_grep("#^{$permission}\(.+\)$#", $permissions)) && !empty($userId)) {
|
||||
$permissions[] = $permission . '(user:' . $userId . ')';
|
||||
}
|
||||
}
|
||||
return $permissions;
|
||||
}
|
||||
|
||||
public static function allowedForUserType(array $permissions): bool
|
||||
{
|
||||
// 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 (Database::PERMISSIONS as $type) {
|
||||
foreach ($permissions as $permission) {
|
||||
if (!\str_starts_with($permission, $type)) {
|
||||
continue;
|
||||
}
|
||||
$role = \str_replace([$type, '(', ')', ' '], '', $permission);
|
||||
if (!Authorization::isRole($role)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function allowedForResourceType(string $resourceType, array $permissions): bool
|
||||
{
|
||||
return match ($resourceType) {
|
||||
'document',
|
||||
'file' => empty(\preg_grep("#^create\(.+\)$#", $permissions)),
|
||||
default => true
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -282,6 +282,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'] = '2022-06-15T13:45:30.496';
|
||||
break;
|
||||
case 'Appwrite\Network\Validator\Email':
|
||||
$node['schema']['type'] = $validator->getType();
|
||||
$node['schema']['format'] = 'email';
|
||||
|
|
@ -447,6 +452,7 @@ class OpenAPI3 extends Format
|
|||
|
||||
switch ($rule['type']) {
|
||||
case 'string':
|
||||
case 'datetime':
|
||||
$type = 'string';
|
||||
break;
|
||||
|
||||
|
|
|
|||
|
|
@ -278,6 +278,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'] = '2022-06-15T13:45:30.496';
|
||||
break;
|
||||
case 'Appwrite\Network\Validator\Email':
|
||||
$node['type'] = $validator->getType();
|
||||
$node['format'] = 'email';
|
||||
|
|
@ -314,6 +319,14 @@ class Swagger2 extends Format
|
|||
];
|
||||
$node['x-example'] = '["read(any)"]';
|
||||
break;
|
||||
case 'Utopia\Database\Validator\Roles':
|
||||
$node['type'] = $validator->getType();
|
||||
$node['collectionFormat'] = 'multi';
|
||||
$node['items'] = [
|
||||
'type' => 'string',
|
||||
];
|
||||
$node['x-example'] = '["any"]';
|
||||
break;
|
||||
case 'Appwrite\Auth\Validator\Password':
|
||||
$node['type'] = $validator->getType();
|
||||
$node['format'] = 'password';
|
||||
|
|
@ -446,6 +459,7 @@ class Swagger2 extends Format
|
|||
|
||||
switch ($rule['type']) {
|
||||
case 'string':
|
||||
case 'datetime':
|
||||
$type = 'string';
|
||||
break;
|
||||
|
||||
|
|
|
|||
|
|
@ -191,12 +191,10 @@ class Usage
|
|||
protected array $periods = [
|
||||
[
|
||||
'key' => '30m',
|
||||
'multiplier' => 1800,
|
||||
'startTime' => '-24 hours',
|
||||
],
|
||||
[
|
||||
'key' => '1d',
|
||||
'multiplier' => 86400,
|
||||
'startTime' => '-90 days',
|
||||
],
|
||||
];
|
||||
|
|
@ -213,7 +211,7 @@ class Usage
|
|||
* Create or update each metric in the stats collection for the given project
|
||||
*
|
||||
* @param string $projectId
|
||||
* @param int $time
|
||||
* @param string $time
|
||||
* @param string $period
|
||||
* @param string $metric
|
||||
* @param int $value
|
||||
|
|
@ -221,7 +219,7 @@ class Usage
|
|||
*
|
||||
* @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');
|
||||
|
|
@ -246,6 +244,8 @@ class Usage
|
|||
$document->setAttribute('value', $value)
|
||||
);
|
||||
}
|
||||
|
||||
$time = (new \DateTime($time))->getTimestamp(); //todo: What about this timestamp?
|
||||
$this->latestTime[$metric][$period] = $time;
|
||||
} catch (\Exception $e) { // if projects are deleted this might fail
|
||||
if (is_callable($this->errorHandler)) {
|
||||
|
|
@ -311,7 +311,7 @@ class Usage
|
|||
}
|
||||
}
|
||||
|
||||
$time = \strtotime($point['time']);
|
||||
$time = $point['time']; //todo: check is this datetime format?
|
||||
$value = (!empty($point['value'])) ? $point['value'] : 0;
|
||||
|
||||
$this->createOrUpdateMetric(
|
||||
|
|
|
|||
|
|
@ -2,8 +2,10 @@
|
|||
|
||||
namespace Appwrite\Stats;
|
||||
|
||||
use Exception;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Query;
|
||||
|
||||
class UsageDB extends Usage
|
||||
{
|
||||
|
|
@ -12,6 +14,7 @@ class UsageDB extends Usage
|
|||
$this->database = $database;
|
||||
$this->errorHandler = $errorHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create or Update Mertic
|
||||
* Create or update each metric in the stats collection for the given project
|
||||
|
|
@ -21,12 +24,22 @@ class UsageDB extends Usage
|
|||
* @param int $value
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
private function createOrUpdateMetric(string $projectId, string $metric, int $value): 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);
|
||||
}
|
||||
|
||||
$id = \md5("{$time}_{$period}_{$metric}");
|
||||
$this->database->setNamespace('_' . $projectId);
|
||||
|
||||
|
|
@ -48,7 +61,7 @@ class UsageDB extends Usage
|
|||
$document->setAttribute('value', $value)
|
||||
);
|
||||
}
|
||||
} catch (\Exception$e) { // if projects are deleted this might fail
|
||||
} catch (\Exception $e) { // if projects are deleted this might fail
|
||||
if (is_callable($this->errorHandler)) {
|
||||
call_user_func($this->errorHandler, $e, "sync_project_{$projectId}_metric_{$metric}");
|
||||
} else {
|
||||
|
|
@ -79,7 +92,11 @@ class UsageDB extends Usage
|
|||
|
||||
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}");
|
||||
|
|
@ -113,6 +130,7 @@ class UsageDB extends Usage
|
|||
* @param string $metric
|
||||
*
|
||||
* @return int
|
||||
* @throws Exception
|
||||
*/
|
||||
private function sum(string $projectId, string $collection, string $attribute, string $metric): int
|
||||
{
|
||||
|
|
@ -122,7 +140,7 @@ class UsageDB extends Usage
|
|||
$sum = (int) $this->database->sum($collection, $attribute);
|
||||
$this->createOrUpdateMetric($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 {
|
||||
|
|
@ -140,6 +158,7 @@ class UsageDB extends Usage
|
|||
* @param string $metric
|
||||
*
|
||||
* @return int
|
||||
* @throws Exception
|
||||
*/
|
||||
private function count(string $projectId, string $collection, string $metric): int
|
||||
{
|
||||
|
|
@ -149,7 +168,7 @@ class UsageDB extends Usage
|
|||
$count = $this->database->count($collection);
|
||||
$this->createOrUpdateMetric($projectId, $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 {
|
||||
|
|
|
|||
|
|
@ -10,17 +10,20 @@ class Queries extends ValidatorQueries
|
|||
/**
|
||||
* Expression constructor
|
||||
*
|
||||
* This Queries Validator that filters indexes for only available indexes
|
||||
*
|
||||
* @param QueryValidator $validator
|
||||
* @param Document[] $attributes
|
||||
* @param Document[] $indexes
|
||||
* @param bool $strict
|
||||
*/
|
||||
public function __construct($attributes, $indexes, $strict)
|
||||
public function __construct($validator, $attributes = [], $indexes = [], $strict = true)
|
||||
{
|
||||
// Remove failed/stuck/processing indexes
|
||||
$indexes = \array_filter($indexes, function ($index) {
|
||||
$availableIndexes = \array_filter($indexes, function ($index) {
|
||||
return $index->getAttribute('status') === 'available';
|
||||
});
|
||||
|
||||
parent::__construct($attributes, $indexes, $strict);
|
||||
parent::__construct($validator, $attributes, $availableIndexes, $strict);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,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;
|
||||
|
|
@ -116,6 +117,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_USER = 'user';
|
||||
|
|
@ -255,6 +257,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.000Z';
|
||||
|
||||
/**
|
||||
* @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,22 +17,22 @@ 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 date 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,
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'Bucket update date in Datetime',
|
||||
'default' => '',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
->addRule('$permissions', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'File permissions.',
|
||||
'default' => [],
|
||||
'example' => [Permission::read(Role::any())],
|
||||
'example' => ['read("any")'],
|
||||
'array' => true,
|
||||
])
|
||||
->addRule('fileSecurity', [
|
||||
|
|
|
|||
|
|
@ -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,22 +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,
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'Collection update date in Datetime',
|
||||
'default' => '',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
->addRule('$permissions', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Collection permissions.',
|
||||
'default' => '',
|
||||
'example' => Permission::read(Role::any()),
|
||||
'example' => ['read("any")'],
|
||||
'array' => true
|
||||
])
|
||||
->addRule('databaseId', [
|
||||
|
|
@ -68,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,22 +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,
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'Document update date in Datetime',
|
||||
'default' => '',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
->addRule('$permissions', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Document write permissions.',
|
||||
'description' => 'Document permissions.',
|
||||
'default' => '',
|
||||
'example' => Permission::read(Role::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,
|
||||
|
|
|
|||
|
|
@ -17,22 +17,22 @@ class Execution extends Model
|
|||
'example' => '5e5ea5c16897e',
|
||||
])
|
||||
->addRule('$createdAt', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Execution creation date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'Execution creation date in Datetime',
|
||||
'default' => '',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
->addRule('$updatedAt', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Execution update date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'Execution upate date in Datetime',
|
||||
'default' => '',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
->addRule('$permissions', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Execution permissions.',
|
||||
'description' => 'Execution roles.',
|
||||
'default' => '',
|
||||
'example' => 'any',
|
||||
'example' => ['any'],
|
||||
'array' => true,
|
||||
])
|
||||
->addRule('functionId', [
|
||||
|
|
|
|||
|
|
@ -23,22 +23,22 @@ class File extends Model
|
|||
'example' => '5e5ea5c16897e',
|
||||
])
|
||||
->addRule('$createdAt', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'File creation date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'File creation date in Datetime',
|
||||
'default' => '',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
->addRule('$updatedAt', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'File update date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'File update date in Datetime',
|
||||
'default' => '',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
->addRule('$permissions', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'File permissions.',
|
||||
'default' => [],
|
||||
'example' => Permission::read(Role::any()),
|
||||
'example' => ['read("any")'],
|
||||
'array' => true,
|
||||
])
|
||||
->addRule('name', [
|
||||
|
|
|
|||
|
|
@ -19,16 +19,16 @@ class Func extends Model
|
|||
'example' => '5e5ea5c16897e',
|
||||
])
|
||||
->addRule('$createdAt', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Function creation date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'Function creation date in Datetime',
|
||||
'default' => '',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
->addRule('$updatedAt', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Function update date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'Function update date in Datetime',
|
||||
'default' => '',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
->addRule('execute', [
|
||||
'type' => self::TYPE_STRING,
|
||||
|
|
@ -81,16 +81,16 @@ class Func extends Model
|
|||
'example' => '5 4 * * *',
|
||||
])
|
||||
->addRule('scheduleNext', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Function next scheduled execution date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981292,
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'Function next scheduled execution date in Datetime.',
|
||||
'default' => '',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
->addRule('schedulePrevious', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Function next scheduled execution date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981237,
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'Function Previous scheduled execution date in Datetime.',
|
||||
'default' => '',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
->addRule('timeout', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
|
|
|
|||
|
|
@ -22,16 +22,16 @@ class Key extends Model
|
|||
'example' => '5e5ea5c16897e',
|
||||
])
|
||||
->addRule('$createdAt', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Key creation date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'Key creation date in Datetime',
|
||||
'default' => '',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
->addRule('$updatedAt', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Key update date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'Key update date in Datetime',
|
||||
'default' => '',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
->addRule('name', [
|
||||
'type' => self::TYPE_STRING,
|
||||
|
|
@ -40,10 +40,10 @@ class Key extends Model
|
|||
'example' => 'My API Key',
|
||||
])
|
||||
->addRule('expire', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Key expiration in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => '1653990687',
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'Key expiration date in Datetime',
|
||||
'default' => '',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
->addRule('scopes', [
|
||||
'type' => self::TYPE_STRING,
|
||||
|
|
|
|||
|
|
@ -47,10 +47,10 @@ class Log extends Model
|
|||
'example' => '127.0.0.1',
|
||||
])
|
||||
->addRule('time', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Log creation time in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'Log creation date in Datetime.',
|
||||
'default' => '',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
->addRule('osCode', [
|
||||
'type' => self::TYPE_STRING,
|
||||
|
|
|
|||
|
|
@ -17,16 +17,16 @@ class Membership extends Model
|
|||
'example' => '5e5ea5c16897e',
|
||||
])
|
||||
->addRule('$createdAt', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Membership creation date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'Membership creation date in Datetime',
|
||||
'default' => '',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
->addRule('$updatedAt', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Membership update date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'Membership update date in Datetime',
|
||||
'default' => '',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
->addRule('userId', [
|
||||
'type' => self::TYPE_STRING,
|
||||
|
|
@ -59,16 +59,16 @@ class Membership extends Model
|
|||
'example' => 'VIP',
|
||||
])
|
||||
->addRule('invited', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Date, the user has been invited to join the team in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'Date, the user has been invited to join the team in Datetime',
|
||||
'default' => '',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
->addRule('joined', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Date, the user has accepted the invitation to join the team in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'Date, the user has accepted the invitation to join the team in Datetime',
|
||||
'default' => '',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
->addRule('confirm', [
|
||||
'type' => self::TYPE_BOOLEAN,
|
||||
|
|
|
|||
|
|
@ -22,16 +22,16 @@ class Platform extends Model
|
|||
'example' => '5e5ea5c16897e',
|
||||
])
|
||||
->addRule('$createdAt', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Project creation date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'Platform creation date in Datetime',
|
||||
'default' => '',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
->addRule('$updatedAt', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Project update date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'Platform update date in Datetime',
|
||||
'default' => '',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
->addRule('name', [
|
||||
'type' => self::TYPE_STRING,
|
||||
|
|
|
|||
|
|
@ -24,16 +24,16 @@ class Project extends Model
|
|||
'example' => '5e5ea5c16897e',
|
||||
])
|
||||
->addRule('$createdAt', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Project creation date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'Project creation date in Datetime',
|
||||
'default' => '',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
->addRule('$updatedAt', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Project update date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'Project update date in Datetime',
|
||||
'default' => '',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
->addRule('name', [
|
||||
'type' => self::TYPE_STRING,
|
||||
|
|
|
|||
|
|
@ -17,10 +17,10 @@ class Session extends Model
|
|||
'example' => '5e5ea5c16897e',
|
||||
])
|
||||
->addRule('$createdAt', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Session creation date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'Session creation date in Datetime',
|
||||
'default' => '',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
->addRule('userId', [
|
||||
'type' => self::TYPE_STRING,
|
||||
|
|
@ -29,10 +29,10 @@ class Session extends Model
|
|||
'example' => '5e5bb8c16897e',
|
||||
])
|
||||
->addRule('expire', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Session expiration date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'Session expiration date in Datetime',
|
||||
'default' => '',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
->addRule('provider', [
|
||||
'type' => self::TYPE_STRING,
|
||||
|
|
@ -53,10 +53,10 @@ class Session extends Model
|
|||
'example' => 'MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI3',
|
||||
])
|
||||
->addRule('providerAccessTokenExpiry', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Date, the Unix timestamp of when the access token expires.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'The date of when the access token expires in datetime format.',
|
||||
'default' => '',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
->addRule('providerRefreshToken', [
|
||||
'type' => self::TYPE_STRING,
|
||||
|
|
|
|||
|
|
@ -17,16 +17,16 @@ class Team extends Model
|
|||
'example' => '5e5ea5c16897e',
|
||||
])
|
||||
->addRule('$createdAt', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Team creation date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'Team creation date in Datetime',
|
||||
'default' => '',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
->addRule('$updatedAt', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Team update date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'Team update date in Datetime',
|
||||
'default' => '',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
->addRule('name', [
|
||||
'type' => self::TYPE_STRING,
|
||||
|
|
|
|||
|
|
@ -17,10 +17,10 @@ class Token extends Model
|
|||
'example' => 'bb8ea5c16897e',
|
||||
])
|
||||
->addRule('$createdAt', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Token creation date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'Token creation date in Datetime',
|
||||
'default' => '',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
->addRule('userId', [
|
||||
'type' => self::TYPE_STRING,
|
||||
|
|
@ -35,10 +35,10 @@ class Token extends Model
|
|||
'example' => '',
|
||||
])
|
||||
->addRule('expire', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'Token expiration date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
'default' => '',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,16 +18,16 @@ class User extends Model
|
|||
'example' => '5e5ea5c16897e',
|
||||
])
|
||||
->addRule('$createdAt', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'User creation date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'User creation date in Datetime.',
|
||||
'default' => '',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
->addRule('$updatedAt', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'User update date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'User update date in Datetime.',
|
||||
'default' => '',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
->addRule('name', [
|
||||
'type' => self::TYPE_STRING,
|
||||
|
|
@ -36,10 +36,10 @@ class User extends Model
|
|||
'example' => 'John Doe',
|
||||
])
|
||||
->addRule('registration', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'User registration date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'User registration date in Datetime.',
|
||||
'default' => null,
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
->addRule('status', [
|
||||
'type' => self::TYPE_BOOLEAN,
|
||||
|
|
@ -48,10 +48,10 @@ class User extends Model
|
|||
'example' => true,
|
||||
])
|
||||
->addRule('passwordUpdate', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Unix timestamp of the most recent password update',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'Datetime of the most recent password update',
|
||||
'default' => '',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
->addRule('email', [
|
||||
'type' => self::TYPE_STRING,
|
||||
|
|
|
|||
|
|
@ -22,16 +22,16 @@ class Webhook extends Model
|
|||
'example' => '5e5ea5c16897e',
|
||||
])
|
||||
->addRule('$createdAt', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Webhook creation date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'Webhook creation date in Datetime',
|
||||
'default' => '',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
->addRule('$updatedAt', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Webhook update date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'Webhook update date in Datetime',
|
||||
'default' => '',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
->addRule('name', [
|
||||
'type' => self::TYPE_STRING,
|
||||
|
|
|
|||
|
|
@ -2,12 +2,14 @@
|
|||
|
||||
namespace Tests\E2E\Scopes;
|
||||
|
||||
use Utopia\Database\ID;
|
||||
|
||||
trait ProjectConsole
|
||||
{
|
||||
public function getProject(): array
|
||||
{
|
||||
return [
|
||||
'$id' => 'console',
|
||||
'$id' => ID::custom('console'),
|
||||
'name' => 'Appwrite',
|
||||
'apiKey' => '',
|
||||
];
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
namespace Tests\E2E\Scopes;
|
||||
|
||||
use Tests\E2E\Client;
|
||||
use Utopia\Database\ID;
|
||||
|
||||
trait ProjectCustom
|
||||
{
|
||||
|
|
@ -26,7 +27,7 @@ trait ProjectCustom
|
|||
'cookie' => 'a_session_console=' . $this->getRoot()['session'],
|
||||
'x-appwrite-project' => 'console',
|
||||
], [
|
||||
'teamId' => 'unique()',
|
||||
'teamId' => ID::unique(),
|
||||
'name' => 'Demo Project Team',
|
||||
]);
|
||||
$this->assertEquals(201, $team['headers']['status-code']);
|
||||
|
|
@ -39,7 +40,7 @@ trait ProjectCustom
|
|||
'cookie' => 'a_session_console=' . $this->getRoot()['session'],
|
||||
'x-appwrite-project' => 'console',
|
||||
], [
|
||||
'projectId' => 'unique()',
|
||||
'projectId' => ID::unique(),
|
||||
'name' => 'Demo Project',
|
||||
'teamId' => $team['body']['$id'],
|
||||
'description' => 'Demo Project Description',
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ namespace Tests\E2E\Scopes;
|
|||
|
||||
use Tests\E2E\Client;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Utopia\Database\ID;
|
||||
|
||||
abstract class Scope extends TestCase
|
||||
{
|
||||
|
|
@ -87,7 +88,7 @@ abstract class Scope extends TestCase
|
|||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => 'console',
|
||||
], [
|
||||
'userId' => 'unique()',
|
||||
'userId' => ID::unique(),
|
||||
'email' => $email,
|
||||
'password' => $password,
|
||||
'name' => $name,
|
||||
|
|
@ -107,7 +108,7 @@ abstract class Scope extends TestCase
|
|||
$session = $this->client->parseCookie((string)$session['headers']['set-cookie'])['a_session_console'];
|
||||
|
||||
self::$root = [
|
||||
'$id' => $root['body']['$id'],
|
||||
'$id' => ID::custom($root['body']['$id']),
|
||||
'name' => $root['body']['name'],
|
||||
'email' => $root['body']['email'],
|
||||
'session' => $session,
|
||||
|
|
@ -139,7 +140,7 @@ abstract class Scope extends TestCase
|
|||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], [
|
||||
'userId' => 'unique()',
|
||||
'userId' => ID::unique(),
|
||||
'email' => $email,
|
||||
'password' => $password,
|
||||
'name' => $name,
|
||||
|
|
@ -159,7 +160,7 @@ abstract class Scope extends TestCase
|
|||
$session = $this->client->parseCookie((string)$session['headers']['set-cookie'])['a_session_' . $this->getProject()['$id']];
|
||||
|
||||
self::$user[$this->getProject()['$id']] = [
|
||||
'$id' => $user['body']['$id'],
|
||||
'$id' => ID::custom($user['body']['$id']),
|
||||
'name' => $user['body']['name'],
|
||||
'email' => $user['body']['email'],
|
||||
'session' => $session,
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@
|
|||
namespace Tests\E2E\Services\Account;
|
||||
|
||||
use Tests\E2E\Client;
|
||||
use Utopia\Database\ID;
|
||||
use Utopia\Database\DateTime;
|
||||
|
||||
trait AccountBase
|
||||
{
|
||||
|
|
@ -20,7 +22,7 @@ trait AccountBase
|
|||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
]), [
|
||||
'userId' => 'unique()',
|
||||
'userId' => ID::unique(),
|
||||
'email' => $email,
|
||||
'password' => $password,
|
||||
'name' => $name,
|
||||
|
|
@ -31,7 +33,7 @@ trait AccountBase
|
|||
$this->assertEquals($response['headers']['status-code'], 201);
|
||||
$this->assertNotEmpty($response['body']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$this->assertIsNumeric($response['body']['registration']);
|
||||
$this->assertEquals(true, DateTime::isValid($response['body']['registration']));
|
||||
$this->assertEquals($response['body']['email'], $email);
|
||||
$this->assertEquals($response['body']['name'], $name);
|
||||
|
||||
|
|
@ -43,7 +45,7 @@ trait AccountBase
|
|||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
]), [
|
||||
'userId' => 'unique()',
|
||||
'userId' => ID::unique(),
|
||||
'email' => $email,
|
||||
'password' => $password,
|
||||
'name' => $name,
|
||||
|
|
@ -56,7 +58,7 @@ trait AccountBase
|
|||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
]), [
|
||||
'userId' => 'unique()',
|
||||
'userId' => ID::unique(),
|
||||
'email' => '',
|
||||
'password' => '',
|
||||
]);
|
||||
|
|
@ -68,7 +70,7 @@ trait AccountBase
|
|||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
]), [
|
||||
'userId' => 'unique()',
|
||||
'userId' => ID::unique(),
|
||||
'email' => $email,
|
||||
'password' => '',
|
||||
]);
|
||||
|
|
@ -80,7 +82,7 @@ trait AccountBase
|
|||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
]), [
|
||||
'userId' => 'unique()',
|
||||
'userId' => ID::unique(),
|
||||
'email' => '',
|
||||
'password' => $password,
|
||||
]);
|
||||
|
|
@ -195,7 +197,7 @@ trait AccountBase
|
|||
$this->assertEquals($response['headers']['status-code'], 200);
|
||||
$this->assertNotEmpty($response['body']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$this->assertIsNumeric($response['body']['registration']);
|
||||
$this->assertEquals(true, DateTime::isValid($response['body']['registration']));
|
||||
$this->assertEquals($response['body']['email'], $email);
|
||||
$this->assertEquals($response['body']['name'], $name);
|
||||
|
||||
|
|
@ -340,7 +342,7 @@ trait AccountBase
|
|||
$this->assertIsNumeric($response['body']['total']);
|
||||
$this->assertContains($response['body']['logs'][1]['event'], ["users.{$userId}.create", "users.{$userId}.sessions.{$sessionId}.create"]);
|
||||
$this->assertEquals($response['body']['logs'][1]['ip'], filter_var($response['body']['logs'][1]['ip'], FILTER_VALIDATE_IP));
|
||||
$this->assertIsNumeric($response['body']['logs'][1]['time']);
|
||||
$this->assertEquals(true, DateTime::isValid($response['body']['logs'][1]['time']));
|
||||
|
||||
$this->assertEquals('Windows', $response['body']['logs'][1]['osName']);
|
||||
$this->assertEquals('WIN', $response['body']['logs'][1]['osCode']);
|
||||
|
|
@ -362,7 +364,7 @@ trait AccountBase
|
|||
|
||||
$this->assertContains($response['body']['logs'][2]['event'], ["users.{$userId}.create", "users.{$userId}.sessions.{$sessionId}.create"]);
|
||||
$this->assertEquals($response['body']['logs'][2]['ip'], filter_var($response['body']['logs'][2]['ip'], FILTER_VALIDATE_IP));
|
||||
$this->assertIsNumeric($response['body']['logs'][2]['time']);
|
||||
$this->assertEquals(true, DateTime::isValid($response['body']['logs'][2]['time']));
|
||||
|
||||
$this->assertEquals('Windows', $response['body']['logs'][2]['osName']);
|
||||
$this->assertEquals('WIN', $response['body']['logs'][2]['osCode']);
|
||||
|
|
@ -474,7 +476,7 @@ trait AccountBase
|
|||
$this->assertIsArray($response['body']);
|
||||
$this->assertNotEmpty($response['body']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$this->assertIsNumeric($response['body']['registration']);
|
||||
$this->assertEquals(true, DateTime::isValid($response['body']['registration']));
|
||||
$this->assertEquals($response['body']['email'], $email);
|
||||
$this->assertEquals($response['body']['name'], $newName);
|
||||
|
||||
|
|
@ -540,7 +542,7 @@ trait AccountBase
|
|||
$this->assertIsArray($response['body']);
|
||||
$this->assertNotEmpty($response['body']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$this->assertIsNumeric($response['body']['registration']);
|
||||
$this->assertEquals(true, DateTime::isValid($response['body']['registration']));
|
||||
$this->assertEquals($response['body']['email'], $email);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
|
||||
|
|
@ -630,7 +632,7 @@ trait AccountBase
|
|||
$this->assertIsArray($response['body']);
|
||||
$this->assertNotEmpty($response['body']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$this->assertIsNumeric($response['body']['registration']);
|
||||
$this->assertEquals(true, DateTime::isValid($response['body']['registration']));
|
||||
$this->assertEquals($response['body']['email'], $newEmail);
|
||||
|
||||
/**
|
||||
|
|
@ -663,7 +665,7 @@ trait AccountBase
|
|||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
]), [
|
||||
'userId' => 'unique()',
|
||||
'userId' => ID::unique(),
|
||||
'email' => $data['email'],
|
||||
'password' => $data['password'],
|
||||
'name' => $data['name'],
|
||||
|
|
@ -672,7 +674,7 @@ trait AccountBase
|
|||
$this->assertEquals($response['headers']['status-code'], 201);
|
||||
$this->assertNotEmpty($response['body']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$this->assertIsNumeric($response['body']['registration']);
|
||||
$this->assertEquals(true, DateTime::isValid($response['body']['registration']));
|
||||
$this->assertEquals($response['body']['email'], $data['email']);
|
||||
$this->assertEquals($response['body']['name'], $data['name']);
|
||||
|
||||
|
|
@ -814,7 +816,7 @@ trait AccountBase
|
|||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$this->assertEmpty($response['body']['secret']);
|
||||
$this->assertIsNumeric($response['body']['expire']);
|
||||
$this->assertEquals(true, DateTime::isValid($response['body']['expire']));
|
||||
|
||||
$lastEmail = $this->getLastEmail();
|
||||
|
||||
|
|
@ -823,9 +825,7 @@ trait AccountBase
|
|||
$this->assertEquals('Account Verification', $lastEmail['subject']);
|
||||
|
||||
$verification = substr($lastEmail['text'], strpos($lastEmail['text'], '&secret=', 0) + 8, 256);
|
||||
|
||||
$expireTime = strpos($lastEmail['text'], 'expire=' . $response['body']['expire'], 0);
|
||||
|
||||
$expireTime = strpos($lastEmail['text'], 'expire=' . urlencode($response['body']['expire']), 0);
|
||||
$this->assertNotFalse($expireTime);
|
||||
|
||||
$secretTest = strpos($lastEmail['text'], 'secret=' . $response['body']['secret'], 0);
|
||||
|
|
@ -899,7 +899,7 @@ trait AccountBase
|
|||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
||||
]), [
|
||||
'userId' => 'ewewe',
|
||||
'userId' => ID::custom('ewewe'),
|
||||
'secret' => $verification,
|
||||
]);
|
||||
|
||||
|
|
@ -1118,7 +1118,7 @@ trait AccountBase
|
|||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$this->assertEmpty($response['body']['secret']);
|
||||
$this->assertIsNumeric($response['body']['expire']);
|
||||
$this->assertEquals(true, DateTime::isValid($response['body']['expire']));
|
||||
|
||||
$lastEmail = $this->getLastEmail();
|
||||
|
||||
|
|
@ -1128,7 +1128,7 @@ trait AccountBase
|
|||
|
||||
$recovery = substr($lastEmail['text'], strpos($lastEmail['text'], '&secret=', 0) + 8, 256);
|
||||
|
||||
$expireTime = strpos($lastEmail['text'], 'expire=' . $response['body']['expire'], 0);
|
||||
$expireTime = strpos($lastEmail['text'], 'expire=' . urlencode($response['body']['expire']), 0);
|
||||
|
||||
$this->assertNotFalse($expireTime);
|
||||
|
||||
|
|
@ -1214,7 +1214,7 @@ trait AccountBase
|
|||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
]), [
|
||||
'userId' => 'ewewe',
|
||||
'userId' => ID::custom('ewewe'),
|
||||
'secret' => $recovery,
|
||||
'password' => $newPassowrd,
|
||||
'passwordAgain' => $newPassowrd,
|
||||
|
|
@ -1263,7 +1263,7 @@ trait AccountBase
|
|||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
]), [
|
||||
'userId' => 'unique()',
|
||||
'userId' => ID::unique(),
|
||||
'email' => $email,
|
||||
// 'url' => 'http://localhost/magiclogin',
|
||||
]);
|
||||
|
|
@ -1271,7 +1271,7 @@ trait AccountBase
|
|||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$this->assertEmpty($response['body']['secret']);
|
||||
$this->assertIsNumeric($response['body']['expire']);
|
||||
$this->assertEquals(true, DateTime::isValid($response['body']['expire']));
|
||||
|
||||
$userId = $response['body']['userId'];
|
||||
|
||||
|
|
@ -1281,7 +1281,7 @@ trait AccountBase
|
|||
|
||||
$token = substr($lastEmail['text'], strpos($lastEmail['text'], '&secret=', 0) + 8, 256);
|
||||
|
||||
$expireTime = strpos($lastEmail['text'], 'expire=' . $response['body']['expire'], 0);
|
||||
$expireTime = strpos($lastEmail['text'], 'expire=' . urlencode($response['body']['expire']), 0);
|
||||
|
||||
$this->assertNotFalse($expireTime);
|
||||
|
||||
|
|
@ -1301,7 +1301,7 @@ trait AccountBase
|
|||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
]), [
|
||||
'userId' => 'unique()',
|
||||
'userId' => ID::unique(),
|
||||
'email' => $email,
|
||||
'url' => 'localhost/magiclogin',
|
||||
]);
|
||||
|
|
@ -1313,7 +1313,7 @@ trait AccountBase
|
|||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
]), [
|
||||
'userId' => 'unique()',
|
||||
'userId' => ID::unique(),
|
||||
'email' => $email,
|
||||
'url' => 'http://remotehost/magiclogin',
|
||||
]);
|
||||
|
|
@ -1377,7 +1377,7 @@ trait AccountBase
|
|||
$this->assertEquals($response['headers']['status-code'], 200);
|
||||
$this->assertNotEmpty($response['body']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$this->assertIsNumeric($response['body']['registration']);
|
||||
$this->assertEquals(true, DateTime::isValid($response['body']['registration']));
|
||||
$this->assertEquals($response['body']['email'], $email);
|
||||
$this->assertTrue($response['body']['emailVerification']);
|
||||
|
||||
|
|
@ -1389,7 +1389,7 @@ trait AccountBase
|
|||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
]), [
|
||||
'userId' => 'ewewe',
|
||||
'userId' => ID::custom('ewewe'),
|
||||
'secret' => $token,
|
||||
]);
|
||||
|
||||
|
|
@ -1437,7 +1437,7 @@ trait AccountBase
|
|||
$this->assertIsArray($response['body']);
|
||||
$this->assertNotEmpty($response['body']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$this->assertIsNumeric($response['body']['registration']);
|
||||
$this->assertEquals(true, DateTime::isValid($response['body']['registration']));
|
||||
$this->assertEquals($response['body']['email'], $email);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ use Tests\E2E\Client;
|
|||
use Tests\E2E\Scopes\Scope;
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Scopes\SideClient;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\ID;
|
||||
|
||||
use function sleep;
|
||||
|
||||
|
|
@ -70,7 +72,7 @@ class AccountCustomClientTest extends Scope
|
|||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
]), [
|
||||
'userId' => 'unique()',
|
||||
'userId' => ID::unique(),
|
||||
'email' => $email,
|
||||
'password' => $password,
|
||||
'name' => $name,
|
||||
|
|
@ -151,7 +153,7 @@ class AccountCustomClientTest extends Scope
|
|||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
]), [
|
||||
'userId' => 'unique()',
|
||||
'userId' => ID::unique(),
|
||||
'email' => $email,
|
||||
'password' => $password,
|
||||
'name' => $name,
|
||||
|
|
@ -230,7 +232,7 @@ class AccountCustomClientTest extends Scope
|
|||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
]), [
|
||||
'userId' => 'unique()',
|
||||
'userId' => ID::unique(),
|
||||
'email' => $email,
|
||||
'password' => $password,
|
||||
'name' => $name,
|
||||
|
|
@ -410,7 +412,7 @@ class AccountCustomClientTest extends Scope
|
|||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
]), [
|
||||
'userId' => 'unique()',
|
||||
'userId' => ID::unique(),
|
||||
'email' => $email,
|
||||
'password' => $password
|
||||
]);
|
||||
|
|
@ -446,7 +448,7 @@ class AccountCustomClientTest extends Scope
|
|||
$this->assertIsArray($response['body']);
|
||||
$this->assertNotEmpty($response['body']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$this->assertIsNumeric($response['body']['registration']);
|
||||
$this->assertEquals(true, DateTime::isValid($response['body']['registration']));
|
||||
$this->assertEquals($response['body']['email'], $email);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
|
||||
|
|
@ -538,7 +540,7 @@ class AccountCustomClientTest extends Scope
|
|||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals('123456', $response['body']['providerAccessToken']);
|
||||
$this->assertEquals('tuvwxyz', $response['body']['providerRefreshToken']);
|
||||
$this->assertGreaterThan(\time() + 14400 - 5, $response['body']['providerAccessTokenExpiry']); // 5 seconds allowed networking delay
|
||||
$this->assertGreaterThan(DateTime::addSeconds(new \DateTime(), 14400 - 5), $response['body']['providerAccessTokenExpiry']); // 5 seconds allowed networking delay
|
||||
|
||||
$initialExpiry = $response['body']['providerAccessTokenExpiry'];
|
||||
|
||||
|
|
@ -689,14 +691,14 @@ class AccountCustomClientTest extends Scope
|
|||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
]), [
|
||||
'userId' => 'unique()',
|
||||
'userId' => ID::unique(),
|
||||
'number' => $number,
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$this->assertEmpty($response['body']['secret']);
|
||||
$this->assertIsNumeric($response['body']['expire']);
|
||||
$this->assertEquals(true, DateTime::isValid($response['body']['expire']));
|
||||
|
||||
$userId = $response['body']['userId'];
|
||||
|
||||
|
|
@ -708,7 +710,7 @@ class AccountCustomClientTest extends Scope
|
|||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
]), [
|
||||
'userId' => 'unique()'
|
||||
'userId' => ID::unique()
|
||||
]);
|
||||
|
||||
$this->assertEquals(400, $response['headers']['status-code']);
|
||||
|
|
@ -737,7 +739,7 @@ class AccountCustomClientTest extends Scope
|
|||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
]), [
|
||||
'userId' => 'ewewe',
|
||||
'userId' => ID::custom('ewewe'),
|
||||
'secret' => $token,
|
||||
]);
|
||||
|
||||
|
|
@ -784,7 +786,7 @@ class AccountCustomClientTest extends Scope
|
|||
$this->assertEquals($response['headers']['status-code'], 200);
|
||||
$this->assertNotEmpty($response['body']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$this->assertIsNumeric($response['body']['registration']);
|
||||
$this->assertEquals(true, DateTime::isValid($response['body']['registration']));
|
||||
$this->assertEquals($response['body']['phone'], $number);
|
||||
$this->assertTrue($response['body']['phoneVerification']);
|
||||
|
||||
|
|
@ -835,7 +837,7 @@ class AccountCustomClientTest extends Scope
|
|||
$this->assertIsArray($response['body']);
|
||||
$this->assertNotEmpty($response['body']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$this->assertIsNumeric($response['body']['registration']);
|
||||
$this->assertEquals(true, DateTime::isValid($response['body']['registration']));
|
||||
$this->assertEquals($response['body']['email'], $email);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
|
||||
|
|
@ -877,7 +879,7 @@ class AccountCustomClientTest extends Scope
|
|||
$this->assertIsArray($response['body']);
|
||||
$this->assertNotEmpty($response['body']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$this->assertIsNumeric($response['body']['registration']);
|
||||
$this->assertEquals(true, DateTime::isValid($response['body']['registration']));
|
||||
$this->assertEquals($response['body']['phone'], $newPhone);
|
||||
|
||||
/**
|
||||
|
|
@ -926,7 +928,7 @@ class AccountCustomClientTest extends Scope
|
|||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$this->assertEmpty($response['body']['secret']);
|
||||
$this->assertIsNumeric($response['body']['expire']);
|
||||
$this->assertEquals(true, DateTime::isValid($response['body']['expire']));
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
|
@ -963,7 +965,7 @@ class AccountCustomClientTest extends Scope
|
|||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
||||
]), [
|
||||
'userId' => 'ewewe',
|
||||
'userId' => ID::custom('ewewe'),
|
||||
'secret' => Mock::$defaultDigits,
|
||||
]);
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ use Tests\E2E\Client;
|
|||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Scopes\Scope;
|
||||
use Tests\E2E\Scopes\SideServer;
|
||||
use Utopia\Database\ID;
|
||||
|
||||
class AccountCustomServerTest extends Scope
|
||||
{
|
||||
|
|
@ -26,7 +27,7 @@ class AccountCustomServerTest extends Scope
|
|||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
], [
|
||||
'userId' => 'unique()',
|
||||
'userId' => ID::unique(),
|
||||
'email' => $email,
|
||||
'password' => $password,
|
||||
'name' => $name,
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -6,6 +6,9 @@ use Tests\E2E\Scopes\Scope;
|
|||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Client;
|
||||
use Tests\E2E\Scopes\SideConsole;
|
||||
use Utopia\Database\ID;
|
||||
use Utopia\Database\Permission;
|
||||
use Utopia\Database\Role;
|
||||
|
||||
class DatabasesConsoleClientTest extends Scope
|
||||
{
|
||||
|
|
@ -19,7 +22,7 @@ class DatabasesConsoleClientTest extends Scope
|
|||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'databaseId' => 'unique()',
|
||||
'databaseId' => ID::unique(),
|
||||
'name' => 'invalidDocumentDatabase',
|
||||
]);
|
||||
$this->assertEquals(201, $database['headers']['status-code']);
|
||||
|
|
@ -33,7 +36,7 @@ class DatabasesConsoleClientTest extends Scope
|
|||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'collectionId' => 'unique()',
|
||||
'collectionId' => ID::unique(),
|
||||
'name' => 'Movies',
|
||||
'permissions' => [
|
||||
Permission::read(Role::any()),
|
||||
|
|
|
|||
|
|
@ -6,6 +6,9 @@ use Tests\E2E\Client;
|
|||
use Tests\E2E\Scopes\Scope;
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Scopes\SideClient;
|
||||
use Utopia\Database\ID;
|
||||
use Utopia\Database\Permission;
|
||||
use Utopia\Database\Role;
|
||||
|
||||
class DatabasesCustomClientTest extends Scope
|
||||
{
|
||||
|
|
@ -32,7 +35,7 @@ class DatabasesCustomClientTest extends Scope
|
|||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'databaseId' => 'permissionCheckDatabase',
|
||||
'databaseId' => ID::custom('permissionCheckDatabase'),
|
||||
'name' => 'Test Database',
|
||||
]);
|
||||
$this->assertEquals(201, $database['headers']['status-code']);
|
||||
|
|
@ -45,7 +48,7 @@ class DatabasesCustomClientTest extends Scope
|
|||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'collectionId' => 'permissionCheck',
|
||||
'collectionId' => ID::custom('permissionCheck'),
|
||||
'name' => 'permissionCheck',
|
||||
'permissions' => [],
|
||||
'documentSecurity' => true,
|
||||
|
|
@ -73,12 +76,12 @@ class DatabasesCustomClientTest extends Scope
|
|||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'documentId' => 'permissionCheckDocument',
|
||||
'documentId' => ID::custom('permissionCheckDocument'),
|
||||
'data' => [
|
||||
'name' => 'AppwriteBeginner',
|
||||
],
|
||||
'permissions' => [
|
||||
Permission::read(Role::user('user2')),
|
||||
Permission::read(Role::user(ID::custom('user2'))),
|
||||
Permission::read(Role::user($userId)),
|
||||
Permission::update(Role::user($userId)),
|
||||
Permission::delete(Role::user($userId)),
|
||||
|
|
|
|||
|
|
@ -7,6 +7,9 @@ use Tests\E2E\Scopes\Scope;
|
|||
use Tests\E2E\Scopes\SideServer;
|
||||
use Tests\E2E\Client;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\ID;
|
||||
use Utopia\Database\Permission;
|
||||
use Utopia\Database\Role;
|
||||
|
||||
class DatabasesCustomServerTest extends Scope
|
||||
{
|
||||
|
|
@ -21,7 +24,7 @@ class DatabasesCustomServerTest extends Scope
|
|||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'databaseId' => 'first',
|
||||
'databaseId' => ID::custom('first'),
|
||||
'name' => 'Test 1',
|
||||
]);
|
||||
$this->assertEquals(201, $test1['headers']['status-code']);
|
||||
|
|
@ -32,7 +35,7 @@ class DatabasesCustomServerTest extends Scope
|
|||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'databaseId' => 'second',
|
||||
'databaseId' => ID::custom('second'),
|
||||
'name' => 'Test 2',
|
||||
]);
|
||||
$this->assertEquals(201, $test2['headers']['status-code']);
|
||||
|
|
@ -172,7 +175,7 @@ class DatabasesCustomServerTest extends Scope
|
|||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'name' => 'Test 1',
|
||||
'databaseId' => 'first',
|
||||
'databaseId' => ID::custom('first'),
|
||||
]);
|
||||
|
||||
$this->assertEquals(409, $response['headers']['status-code']);
|
||||
|
|
@ -233,7 +236,7 @@ class DatabasesCustomServerTest extends Scope
|
|||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'databaseId' => 'unique()',
|
||||
'databaseId' => ID::unique(),
|
||||
'name' => 'invalidDocumentDatabase',
|
||||
]);
|
||||
$this->assertEquals(201, $database['headers']['status-code']);
|
||||
|
|
@ -249,7 +252,7 @@ class DatabasesCustomServerTest extends Scope
|
|||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'name' => 'Test 1',
|
||||
'collectionId' => 'first',
|
||||
'collectionId' => ID::custom('first'),
|
||||
'permissions' => [
|
||||
Permission::read(Role::any()),
|
||||
Permission::create(Role::any()),
|
||||
|
|
@ -265,7 +268,7 @@ class DatabasesCustomServerTest extends Scope
|
|||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'name' => 'Test 2',
|
||||
'collectionId' => 'second',
|
||||
'collectionId' => ID::custom('second'),
|
||||
'permissions' => [
|
||||
Permission::read(Role::any()),
|
||||
Permission::create(Role::any()),
|
||||
|
|
@ -409,7 +412,7 @@ class DatabasesCustomServerTest extends Scope
|
|||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'name' => 'Test 1',
|
||||
'collectionId' => 'first',
|
||||
'collectionId' => ID::custom('first'),
|
||||
'permissions' => [
|
||||
Permission::read(Role::any()),
|
||||
Permission::create(Role::any()),
|
||||
|
|
@ -429,7 +432,7 @@ class DatabasesCustomServerTest extends Scope
|
|||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'databaseId' => 'unique()',
|
||||
'databaseId' => ID::unique(),
|
||||
'name' => 'invalidDocumentDatabase',
|
||||
]);
|
||||
$this->assertEquals(201, $database['headers']['status-code']);
|
||||
|
|
@ -446,7 +449,7 @@ class DatabasesCustomServerTest extends Scope
|
|||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'collectionId' => 'unique()',
|
||||
'collectionId' => ID::unique(),
|
||||
'name' => 'Actors',
|
||||
'permissions' => [
|
||||
Permission::read(Role::any()),
|
||||
|
|
@ -499,7 +502,7 @@ class DatabasesCustomServerTest extends Scope
|
|||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'documentId' => 'unique()',
|
||||
'documentId' => ID::unique(),
|
||||
'data' => [
|
||||
'firstName' => 'lorem',
|
||||
'lastName' => 'ipsum',
|
||||
|
|
@ -717,7 +720,7 @@ class DatabasesCustomServerTest extends Scope
|
|||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'databaseId' => 'unique()',
|
||||
'databaseId' => ID::unique(),
|
||||
'name' => 'invalidDocumentDatabase',
|
||||
]);
|
||||
$this->assertEquals(201, $database['headers']['status-code']);
|
||||
|
|
@ -729,7 +732,7 @@ class DatabasesCustomServerTest extends Scope
|
|||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'collectionId' => 'unique()',
|
||||
'collectionId' => ID::unique(),
|
||||
'name' => 'TestCleanupDuplicateIndexOnDeleteAttribute',
|
||||
'permissions' => [
|
||||
Permission::read(Role::any()),
|
||||
|
|
@ -849,7 +852,7 @@ class DatabasesCustomServerTest extends Scope
|
|||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'documentId' => 'unique()',
|
||||
'documentId' => ID::unique(),
|
||||
'data' => [
|
||||
'firstName' => 'Tom',
|
||||
'lastName' => 'Holland',
|
||||
|
|
@ -865,7 +868,7 @@ class DatabasesCustomServerTest extends Scope
|
|||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'documentId' => 'unique()',
|
||||
'documentId' => ID::unique(),
|
||||
'data' => [
|
||||
'firstName' => 'Samuel',
|
||||
'lastName' => 'Jackson',
|
||||
|
|
@ -924,7 +927,7 @@ class DatabasesCustomServerTest extends Scope
|
|||
// 'x-appwrite-project' => $this->getProject()['$id'],
|
||||
// 'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
// ]), [
|
||||
// 'collectionId' => 'unique()',
|
||||
// 'collectionId' => ID::unique(),
|
||||
// 'name' => 'attributeCountLimit',
|
||||
// 'read' => ['any'],
|
||||
// 'write' => ['any'],
|
||||
|
|
@ -969,7 +972,7 @@ class DatabasesCustomServerTest extends Scope
|
|||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'databaseId' => 'unique()',
|
||||
'databaseId' => ID::unique(),
|
||||
'name' => 'invalidDocumentDatabase',
|
||||
]);
|
||||
$this->assertEquals(201, $database['headers']['status-code']);
|
||||
|
|
@ -981,7 +984,7 @@ class DatabasesCustomServerTest extends Scope
|
|||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'collectionId' => 'attributeRowWidthLimit',
|
||||
'collectionId' => ID::custom('attributeRowWidthLimit'),
|
||||
'name' => 'attributeRowWidthLimit',
|
||||
'permissions' => [
|
||||
Permission::read(Role::any()),
|
||||
|
|
@ -1035,7 +1038,7 @@ class DatabasesCustomServerTest extends Scope
|
|||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'databaseId' => 'unique()',
|
||||
'databaseId' => ID::unique(),
|
||||
'name' => 'invalidDocumentDatabase',
|
||||
]);
|
||||
$this->assertEquals(201, $database['headers']['status-code']);
|
||||
|
|
@ -1047,7 +1050,7 @@ class DatabasesCustomServerTest extends Scope
|
|||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'collectionId' => 'testLimitException',
|
||||
'collectionId' => ID::custom('testLimitException'),
|
||||
'name' => 'testLimitException',
|
||||
'permissions' => [
|
||||
Permission::read(Role::any()),
|
||||
|
|
|
|||
|
|
@ -6,6 +6,9 @@ use Tests\E2E\Client;
|
|||
use Tests\E2E\Scopes\Scope;
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Scopes\SideClient;
|
||||
use Utopia\Database\ID;
|
||||
use Utopia\Database\Permission;
|
||||
use Utopia\Database\Role;
|
||||
|
||||
class DatabasesPermissionsGuestTest extends Scope
|
||||
{
|
||||
|
|
@ -20,7 +23,7 @@ class DatabasesPermissionsGuestTest extends Scope
|
|||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'databaseId' => 'unique()',
|
||||
'databaseId' => ID::unique(),
|
||||
'name' => 'InvalidDocumentDatabase',
|
||||
]);
|
||||
$this->assertEquals(201, $database['headers']['status-code']);
|
||||
|
|
@ -28,7 +31,7 @@ class DatabasesPermissionsGuestTest extends Scope
|
|||
|
||||
$databaseId = $database['body']['$id'];
|
||||
$movies = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', $this->getServerHeader(), [
|
||||
'collectionId' => 'unique()',
|
||||
'collectionId' => ID::unique(),
|
||||
'name' => 'Movies',
|
||||
'permissions' => [
|
||||
Permission::read(Role::any()),
|
||||
|
|
@ -76,7 +79,7 @@ class DatabasesPermissionsGuestTest extends Scope
|
|||
$collectionId = $data['collectionId'];
|
||||
$databaseId = $data['databaseId'];
|
||||
$response = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/documents', $this->getServerHeader(), [
|
||||
'documentId' => 'unique()',
|
||||
'documentId' => ID::unique(),
|
||||
'data' => [
|
||||
'title' => 'Lorem',
|
||||
],
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ use Tests\E2E\Client;
|
|||
use Tests\E2E\Scopes\Scope;
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Scopes\SideClient;
|
||||
use Utopia\Database\ID;
|
||||
use Utopia\Database\Permission;
|
||||
use Utopia\Database\Role;
|
||||
|
||||
|
|
@ -30,10 +31,10 @@ class DatabasesPermissionsMemberTest extends Scope
|
|||
return [
|
||||
[[Permission::read(Role::any())]],
|
||||
[[Permission::read(Role::users())]],
|
||||
[[Permission::read(Role::user('random'))]],
|
||||
[[Permission::read(Role::user('lorem')), Permission::update(Role::user('lorem')), Permission::delete(Role::user('lorem'))]],
|
||||
[[Permission::read(Role::user('dolor')), Permission::update(Role::user('dolor')), Permission::delete(Role::user('dolor'))]],
|
||||
[[Permission::read(Role::user('dolor')), Permission::read(Role::user('lorem')), Permission::update(Role::user('dolor')), Permission::delete(Role::user('dolor'))]],
|
||||
[[Permission::read(Role::user(ID::custom('random')))]],
|
||||
[[Permission::read(Role::user(ID::custom('lorem'))), Permission::update(Role::user('lorem')), Permission::delete(Role::user('lorem'))]],
|
||||
[[Permission::read(Role::user(ID::custom('dolor'))), Permission::update(Role::user('dolor')), Permission::delete(Role::user('dolor'))]],
|
||||
[[Permission::read(Role::user(ID::custom('dolor'))), Permission::read(Role::user('lorem')), Permission::update(Role::user('dolor')), Permission::delete(Role::user('dolor'))]],
|
||||
[[Permission::update(Role::any()), Permission::delete(Role::any())]],
|
||||
[[Permission::read(Role::any()), Permission::update(Role::any()), Permission::delete(Role::any())]],
|
||||
[[Permission::read(Role::users()), Permission::update(Role::users()), Permission::delete(Role::users())]],
|
||||
|
|
@ -54,7 +55,7 @@ class DatabasesPermissionsMemberTest extends Scope
|
|||
$this->createUsers();
|
||||
|
||||
$db = $this->client->call(Client::METHOD_POST, '/databases', $this->getServerHeader(), [
|
||||
'databaseId' => 'unique()',
|
||||
'databaseId' => ID::unique(),
|
||||
'name' => 'Test Database',
|
||||
]);
|
||||
$this->assertEquals(201, $db['headers']['status-code']);
|
||||
|
|
@ -62,7 +63,7 @@ class DatabasesPermissionsMemberTest extends Scope
|
|||
$databaseId = $db['body']['$id'];
|
||||
|
||||
$public = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', $this->getServerHeader(), [
|
||||
'collectionId' => 'unique()',
|
||||
'collectionId' => ID::unique(),
|
||||
'name' => 'Movies',
|
||||
'permissions' => [
|
||||
Permission::read(Role::any()),
|
||||
|
|
@ -84,7 +85,7 @@ class DatabasesPermissionsMemberTest extends Scope
|
|||
$this->assertEquals(202, $response['headers']['status-code']);
|
||||
|
||||
$private = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', $this->getServerHeader(), [
|
||||
'collectionId' => 'unique()',
|
||||
'collectionId' => ID::unique(),
|
||||
'name' => 'Private Movies',
|
||||
'permissions' => [
|
||||
Permission::read(Role::users()),
|
||||
|
|
@ -126,7 +127,7 @@ class DatabasesPermissionsMemberTest extends Scope
|
|||
$databaseId = $data['databaseId'];
|
||||
|
||||
$response = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collections['public'] . '/documents', $this->getServerHeader(), [
|
||||
'documentId' => 'unique()',
|
||||
'documentId' => ID::unique(),
|
||||
'data' => [
|
||||
'title' => 'Lorem',
|
||||
],
|
||||
|
|
@ -135,7 +136,7 @@ class DatabasesPermissionsMemberTest extends Scope
|
|||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collections['private'] . '/documents', $this->getServerHeader(), [
|
||||
'documentId' => 'unique()',
|
||||
'documentId' => ID::unique(),
|
||||
'data' => [
|
||||
'title' => 'Lorem',
|
||||
],
|
||||
|
|
@ -144,7 +145,7 @@ class DatabasesPermissionsMemberTest extends Scope
|
|||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
|
||||
/**
|
||||
* Check role:all collection
|
||||
* Check "any" collection
|
||||
*/
|
||||
$documents = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $collections['public'] . '/documents', [
|
||||
'origin' => 'http://localhost',
|
||||
|
|
|
|||
|
|
@ -6,6 +6,9 @@ use Tests\E2E\Client;
|
|||
use Tests\E2E\Scopes\Scope;
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Scopes\SideClient;
|
||||
use Utopia\Database\ID;
|
||||
use Utopia\Database\Permission;
|
||||
use Utopia\Database\Role;
|
||||
|
||||
class DatabasesPermissionsTeamTest extends Scope
|
||||
{
|
||||
|
|
@ -42,7 +45,7 @@ class DatabasesPermissionsTeamTest extends Scope
|
|||
$this->assertEquals(201, $db['headers']['status-code']);
|
||||
|
||||
$collection1 = $this->client->call(Client::METHOD_POST, '/databases/' . $this->databaseId . '/collections', $this->getServerHeader(), [
|
||||
'collectionId' => 'collection1',
|
||||
'collectionId' => ID::custom('collection1'),
|
||||
'name' => 'Collection 1',
|
||||
'permissions' => [
|
||||
Permission::read(Role::team($teams['team1']['$id'])),
|
||||
|
|
@ -61,7 +64,7 @@ class DatabasesPermissionsTeamTest extends Scope
|
|||
]);
|
||||
|
||||
$collection2 = $this->client->call(Client::METHOD_POST, '/databases/' . $this->databaseId . '/collections', $this->getServerHeader(), [
|
||||
'collectionId' => 'collection2',
|
||||
'collectionId' => ID::custom('collection2'),
|
||||
'name' => 'Collection 2',
|
||||
'permissions' => [
|
||||
Permission::read(Role::team($teams['team2']['$id'])),
|
||||
|
|
@ -138,7 +141,7 @@ class DatabasesPermissionsTeamTest extends Scope
|
|||
$this->createCollections($this->teams);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_POST, '/databases/' . $this->databaseId . '/collections/' . $this->collections['collection1'] . '/documents', $this->getServerHeader(), [
|
||||
'documentId' => 'unique()',
|
||||
'documentId' => ID::unique(),
|
||||
'data' => [
|
||||
'title' => 'Lorem',
|
||||
],
|
||||
|
|
@ -146,7 +149,7 @@ class DatabasesPermissionsTeamTest extends Scope
|
|||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_POST, '/databases/' . $this->databaseId . '/collections/' . $this->collections['collection2'] . '/documents', $this->getServerHeader(), [
|
||||
'documentId' => 'unique()',
|
||||
'documentId' => ID::unique(),
|
||||
'data' => [
|
||||
'title' => 'Ipsum',
|
||||
],
|
||||
|
|
@ -189,7 +192,7 @@ class DatabasesPermissionsTeamTest extends Scope
|
|||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $users[$user]['session'],
|
||||
], [
|
||||
'documentId' => 'unique()',
|
||||
'documentId' => ID::unique(),
|
||||
'data' => [
|
||||
'title' => 'Ipsum',
|
||||
],
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ use Tests\E2E\Scopes\Scope;
|
|||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Client;
|
||||
use Tests\E2E\Scopes\SideConsole;
|
||||
use Utopia\Database\ID;
|
||||
|
||||
class FunctionsConsoleClientTest extends Scope
|
||||
{
|
||||
|
|
@ -18,7 +19,7 @@ class FunctionsConsoleClientTest extends Scope
|
|||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'functionId' => 'unique()',
|
||||
'functionId' => ID::unique(),
|
||||
'name' => 'Test',
|
||||
'execute' => ["user:{$this->getUser()['$id']}"],
|
||||
'runtime' => 'php-8.0',
|
||||
|
|
@ -41,7 +42,7 @@ class FunctionsConsoleClientTest extends Scope
|
|||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'functionId' => 'unique()',
|
||||
'functionId' => ID::unique(),
|
||||
'name' => 'Test Failure',
|
||||
'execute' => ['some-random-string'],
|
||||
'runtime' => 'php-8.0'
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ use Tests\E2E\Scopes\Scope;
|
|||
use Tests\E2E\Scopes\SideClient;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\ID;
|
||||
|
||||
class FunctionsCustomClientTest extends Scope
|
||||
{
|
||||
|
|
@ -25,7 +26,7 @@ class FunctionsCustomClientTest extends Scope
|
|||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'functionId' => 'unique()',
|
||||
'functionId' => ID::unique(),
|
||||
'name' => 'Test',
|
||||
'vars' => [
|
||||
'funcKey1' => 'funcValue1',
|
||||
|
|
@ -55,7 +56,7 @@ class FunctionsCustomClientTest extends Scope
|
|||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
], [
|
||||
'functionId' => 'unique()',
|
||||
'functionId' => ID::unique(),
|
||||
'name' => 'Test',
|
||||
'execute' => ["user:{$this->getUser()['$id']}"],
|
||||
'runtime' => 'php-8.0',
|
||||
|
|
@ -145,7 +146,7 @@ class FunctionsCustomClientTest extends Scope
|
|||
'x-appwrite-project' => $projectId,
|
||||
'x-appwrite-key' => $apikey,
|
||||
], [
|
||||
'functionId' => 'unique()',
|
||||
'functionId' => ID::unique(),
|
||||
'name' => 'Test',
|
||||
'execute' => ['any'],
|
||||
'runtime' => 'php-8.0',
|
||||
|
|
@ -177,7 +178,7 @@ class FunctionsCustomClientTest extends Scope
|
|||
$deploymentId = $deployment['body']['$id'] ?? '';
|
||||
|
||||
// Wait for deployment to be built.
|
||||
sleep(5);
|
||||
sleep(10);
|
||||
|
||||
$this->assertEquals(202, $deployment['headers']['status-code']);
|
||||
|
||||
|
|
@ -236,7 +237,7 @@ class FunctionsCustomClientTest extends Scope
|
|||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
], [
|
||||
'functionId' => 'unique()',
|
||||
'functionId' => ID::unique(),
|
||||
'name' => 'Test',
|
||||
'execute' => [],
|
||||
'runtime' => 'php-8.0',
|
||||
|
|
@ -330,7 +331,7 @@ class FunctionsCustomClientTest extends Scope
|
|||
'x-appwrite-project' => $projectId,
|
||||
'x-appwrite-key' => $apikey,
|
||||
], [
|
||||
'functionId' => 'unique()',
|
||||
'functionId' => ID::unique(),
|
||||
'name' => 'Test',
|
||||
'execute' => ['any'],
|
||||
'runtime' => 'php-8.0',
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue