mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Merge branch 'master' of https://github.com/appwrite/appwrite into feat-pls-merge
This commit is contained in:
+53
-22
@@ -10,38 +10,41 @@ class Auth
|
||||
/**
|
||||
* User Roles.
|
||||
*/
|
||||
const USER_ROLE_ALL = 'all';
|
||||
const USER_ROLE_GUEST = 'guest';
|
||||
const USER_ROLE_MEMBER = 'member';
|
||||
const USER_ROLE_ADMIN = 'admin';
|
||||
const USER_ROLE_DEVELOPER = 'developer';
|
||||
const USER_ROLE_OWNER = 'owner';
|
||||
const USER_ROLE_APP = 'app';
|
||||
const USER_ROLE_SYSTEM = 'system';
|
||||
public const USER_ROLE_ALL = 'all';
|
||||
public const USER_ROLE_GUEST = 'guest';
|
||||
public const USER_ROLE_MEMBER = 'member';
|
||||
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_SYSTEM = 'system';
|
||||
|
||||
/**
|
||||
* Token Types.
|
||||
*/
|
||||
const TOKEN_TYPE_LOGIN = 1; // Deprecated
|
||||
const TOKEN_TYPE_VERIFICATION = 2;
|
||||
const TOKEN_TYPE_RECOVERY = 3;
|
||||
const TOKEN_TYPE_INVITE = 4;
|
||||
const TOKEN_TYPE_MAGIC_URL = 5;
|
||||
public const TOKEN_TYPE_LOGIN = 1; // Deprecated
|
||||
public const TOKEN_TYPE_VERIFICATION = 2;
|
||||
public const TOKEN_TYPE_RECOVERY = 3;
|
||||
public const TOKEN_TYPE_INVITE = 4;
|
||||
public const TOKEN_TYPE_MAGIC_URL = 5;
|
||||
public const TOKEN_TYPE_PHONE = 6;
|
||||
|
||||
/**
|
||||
* Session Providers.
|
||||
*/
|
||||
const SESSION_PROVIDER_EMAIL = 'email';
|
||||
const SESSION_PROVIDER_ANONYMOUS = 'anonymous';
|
||||
const SESSION_PROVIDER_MAGIC_URL = 'magic-url';
|
||||
public const SESSION_PROVIDER_EMAIL = 'email';
|
||||
public const SESSION_PROVIDER_ANONYMOUS = 'anonymous';
|
||||
public const SESSION_PROVIDER_MAGIC_URL = 'magic-url';
|
||||
public const SESSION_PROVIDER_PHONE = 'phone';
|
||||
|
||||
/**
|
||||
* Token Expiration times.
|
||||
*/
|
||||
const TOKEN_EXPIRATION_LOGIN_LONG = 31536000; /* 1 year */
|
||||
const TOKEN_EXPIRATION_LOGIN_SHORT = 3600; /* 1 hour */
|
||||
const TOKEN_EXPIRATION_RECOVERY = 3600; /* 1 hour */
|
||||
const TOKEN_EXPIRATION_CONFIRM = 3600 * 24 * 7; /* 7 days */
|
||||
public const TOKEN_EXPIRATION_LOGIN_LONG = 31536000; /* 1 year */
|
||||
public const TOKEN_EXPIRATION_LOGIN_SHORT = 3600; /* 1 hour */
|
||||
public const TOKEN_EXPIRATION_RECOVERY = 3600; /* 1 hour */
|
||||
public const TOKEN_EXPIRATION_CONFIRM = 3600 * 24 * 7; /* 7 days */
|
||||
public const TOKEN_EXPIRATION_PHONE = 60 * 15; /* 15 minutes */
|
||||
|
||||
/**
|
||||
* @var string
|
||||
@@ -195,7 +198,8 @@ class Auth
|
||||
*/
|
||||
public static function tokenVerify(array $tokens, int $type, string $secret)
|
||||
{
|
||||
foreach ($tokens as $token) { /** @var Document $token */
|
||||
foreach ($tokens as $token) {
|
||||
/** @var Document $token */
|
||||
if (
|
||||
$token->isSet('type') &&
|
||||
$token->isSet('secret') &&
|
||||
@@ -211,6 +215,25 @@ class Auth
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function phoneTokenVerify(array $tokens, string $secret)
|
||||
{
|
||||
foreach ($tokens as $token) {
|
||||
/** @var Document $token */
|
||||
if (
|
||||
$token->isSet('type') &&
|
||||
$token->isSet('secret') &&
|
||||
$token->isSet('expire') &&
|
||||
$token->getAttribute('type') == Auth::TOKEN_TYPE_PHONE &&
|
||||
$token->getAttribute('secret') === $secret &&
|
||||
$token->getAttribute('expire') >= \time()
|
||||
) {
|
||||
return (string) $token->getId();
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify session and check that its not expired.
|
||||
*
|
||||
@@ -221,7 +244,8 @@ class Auth
|
||||
*/
|
||||
public static function sessionVerify(array $sessions, string $secret)
|
||||
{
|
||||
foreach ($sessions as $session) { /** @var Document $session */
|
||||
foreach ($sessions as $session) {
|
||||
/** @var Document $session */
|
||||
if (
|
||||
$session->isSet('secret') &&
|
||||
$session->isSet('expire') &&
|
||||
@@ -303,4 +327,11 @@ class Auth
|
||||
|
||||
return $roles;
|
||||
}
|
||||
|
||||
public static function isAnonymousUser(Document $user): bool
|
||||
{
|
||||
return (is_null($user->getAttribute('email'))
|
||||
|| is_null($user->getAttribute('phone'))
|
||||
) && is_null($user->getAttribute('password'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Auth\OAuth2;
|
||||
|
||||
use Appwrite\Auth\OAuth2;
|
||||
|
||||
class Autodesk extends OAuth2
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected array $user = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected array $tokens = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected array $scopes = [
|
||||
'user-profile:read',
|
||||
];
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return 'autodesk';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getLoginURL(): string
|
||||
{
|
||||
return 'https://developer.api.autodesk.com/authentication/v1/authorize?' . \http_build_query([
|
||||
'client_id' => $this->appID,
|
||||
'scope' => \implode(' ', $this->getScopes()),
|
||||
'state' => \json_encode($this->state),
|
||||
'redirect_uri' => $this->callback,
|
||||
'response_type' => 'code'
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $code
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getTokens(string $code): array
|
||||
{
|
||||
if (empty($this->tokens)) {
|
||||
$headers = ['Content-Type: application/x-www-form-urlencoded'];
|
||||
$response = $this->request(
|
||||
'POST',
|
||||
'https://developer.api.autodesk.com/authentication/v1/gettoken',
|
||||
$headers,
|
||||
\http_build_query([
|
||||
'client_id' => $this->appID,
|
||||
'redirect_uri' => $this->callback,
|
||||
'client_secret' => $this->appSecret,
|
||||
'code' => $code,
|
||||
'grant_type' => 'authorization_code'
|
||||
])
|
||||
);
|
||||
|
||||
$this->tokens = \json_decode($response, true);
|
||||
}
|
||||
|
||||
return $this->tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $refreshToken
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function refreshTokens(string $refreshToken): array
|
||||
{
|
||||
$response = $this->request(
|
||||
'POST',
|
||||
'https://developer.api.autodesk.com/authentication/v1/refreshtoken',
|
||||
[],
|
||||
\http_build_query([
|
||||
'client_id' => $this->appID,
|
||||
'client_secret' => $this->appSecret,
|
||||
'grant_type' => 'refresh_token',
|
||||
'code' => $code,
|
||||
'redirect_uri' => $this->callback,
|
||||
])
|
||||
);
|
||||
|
||||
$output = [];
|
||||
\parse_str($response, $output);
|
||||
$this->tokens = $output;
|
||||
|
||||
if (empty($this->tokens['refresh_token'])) {
|
||||
$this->tokens['refresh_token'] = $refreshToken;
|
||||
}
|
||||
|
||||
return $this->tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $accessToken
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUserID(string $accessToken): string
|
||||
{
|
||||
$user = $this->getUser($accessToken);
|
||||
|
||||
return $user['userId'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $accessToken
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUserEmail(string $accessToken): string
|
||||
{
|
||||
$user = $this->getUser($accessToken);
|
||||
|
||||
return $user['emailId'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the OAuth email is verified
|
||||
*
|
||||
* @link https://docs.github.com/en/rest/users/emails#list-email-addresses-for-the-authenticated-user
|
||||
*
|
||||
* @param string $accessToken
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isEmailVerified(string $accessToken): bool
|
||||
{
|
||||
$user = $this->getUser($accessToken);
|
||||
|
||||
if ($user['emailVerified'] ?? false) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $accessToken
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUserName(string $accessToken): string
|
||||
{
|
||||
$user = $this->getUser($accessToken);
|
||||
|
||||
return $user['userName'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $accessToken
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getUser(string $accessToken): array
|
||||
{
|
||||
if (empty($this->user)) {
|
||||
$headers = ['Authorization: Bearer ' . \urlencode($accessToken)];
|
||||
$user = $this->request('GET', 'https://developer.api.autodesk.com/userprofile/v1/users/@me', $headers);
|
||||
$this->user = \json_decode($user, true);
|
||||
}
|
||||
|
||||
return $this->user;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Auth\OAuth2;
|
||||
|
||||
use Appwrite\Auth\OAuth2;
|
||||
|
||||
// Reference Material
|
||||
// https://developers.dailymotion.com/api/#authentication
|
||||
|
||||
class Dailymotion extends OAuth2
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private string $endpoint = 'https://api.dailymotion.com';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private string $authEndpoint = 'https://www.dailymotion.com/oauth/authorize';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected array $scopes = [
|
||||
'userinfo',
|
||||
'email'
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected array $fields = [
|
||||
'email',
|
||||
'id',
|
||||
'fullname',
|
||||
'verified'
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected array $user = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected array $tokens = [];
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return 'dailymotion';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getFields(): array
|
||||
{
|
||||
return $this->fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getLoginURL(): string
|
||||
{
|
||||
$url = $this->authEndpoint . '?' .
|
||||
\http_build_query([
|
||||
'response_type' => 'code',
|
||||
'client_id' => $this->appID,
|
||||
'state' => \json_encode($this->state),
|
||||
'redirect_uri' => $this->callback,
|
||||
'scope' => \implode(' ', $this->getScopes())
|
||||
]);
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $code
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getTokens(string $code): array
|
||||
{
|
||||
if (empty($this->tokens)) {
|
||||
$this->tokens = \json_decode($this->request(
|
||||
'POST',
|
||||
$this->endpoint . '/oauth/token',
|
||||
["Content-Type: application/x-www-form-urlencoded"],
|
||||
\http_build_query([
|
||||
'grant_type' => 'authorization_code',
|
||||
"client_id" => $this->appID,
|
||||
"client_secret" => $this->appSecret,
|
||||
"redirect_uri" => $this->callback,
|
||||
'code' => $code,
|
||||
'scope' => \implode(' ', $this->getScopes()),
|
||||
])
|
||||
), true);
|
||||
}
|
||||
return $this->tokens;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $refreshToken
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function refreshTokens(string $refreshToken): array
|
||||
{
|
||||
|
||||
$this->tokens = \json_decode($this->request(
|
||||
'POST',
|
||||
$this->endpoint . '/oauth/token',
|
||||
['Content-Type: application/x-www-form-urlencoded'],
|
||||
\http_build_query([
|
||||
'grant_type' => 'refresh_token',
|
||||
'refresh_token' => $refreshToken,
|
||||
'client_id' => $this->appID,
|
||||
'client_secret' => $this->appSecret,
|
||||
])
|
||||
), true);
|
||||
|
||||
if (empty($this->tokens['refresh_token'])) {
|
||||
$this->tokens['refresh_token'] = $refreshToken;
|
||||
}
|
||||
|
||||
|
||||
return $this->tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $accessToken
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUserID(string $accessToken): string
|
||||
{
|
||||
$user = $this->getUser($accessToken);
|
||||
|
||||
$userId = $user['id'] ?? '';
|
||||
|
||||
return $userId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $accessToken
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUserEmail(string $accessToken): string
|
||||
{
|
||||
$user = $this->getUser($accessToken);
|
||||
$userEmail = $user['email'] ?? '';
|
||||
|
||||
return $userEmail;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the OAuth email is verified
|
||||
*
|
||||
* @link https://developers.dailymotion.com/api/#user-fields
|
||||
*
|
||||
* @param string $accessToken
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isEmailVerified(string $accessToken): bool
|
||||
{
|
||||
$user = $this->getUser($accessToken);
|
||||
|
||||
return $user['verified'] ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $accessToken
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUserName(string $accessToken): string
|
||||
{
|
||||
$user = $this->getUser($accessToken);
|
||||
|
||||
|
||||
$username = $user['fullname'] ?? '';
|
||||
|
||||
return $username;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $accessToken
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getUser(string $accessToken): array
|
||||
{
|
||||
if (empty($this->user)) {
|
||||
$user = $this->request(
|
||||
'GET',
|
||||
$this->endpoint . '/user/me?fields=' . \implode(',', $this->getFields()),
|
||||
['Authorization: Bearer ' . \urlencode($accessToken)],
|
||||
);
|
||||
$this->user = \json_decode($user, true);
|
||||
}
|
||||
|
||||
return $this->user;
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,7 @@ class Gitlab extends OAuth2
|
||||
*/
|
||||
public function getLoginURL(): string
|
||||
{
|
||||
return 'https://gitlab.com/oauth/authorize?' . \http_build_query([
|
||||
return $this->getEndpoint() . '/oauth/authorize?' . \http_build_query([
|
||||
'client_id' => $this->appID,
|
||||
'redirect_uri' => $this->callback,
|
||||
'scope' => \implode(' ', $this->getScopes()),
|
||||
@@ -58,10 +58,10 @@ class Gitlab extends OAuth2
|
||||
if (empty($this->tokens)) {
|
||||
$this->tokens = \json_decode($this->request(
|
||||
'POST',
|
||||
'https://gitlab.com/oauth/token?' . \http_build_query([
|
||||
$this->getEndpoint() . '/oauth/token?' . \http_build_query([
|
||||
'code' => $code,
|
||||
'client_id' => $this->appID,
|
||||
'client_secret' => $this->appSecret,
|
||||
'client_secret' => $this->getAppSecret()['clientSecret'],
|
||||
'redirect_uri' => $this->callback,
|
||||
'grant_type' => 'authorization_code'
|
||||
])
|
||||
@@ -80,10 +80,10 @@ class Gitlab extends OAuth2
|
||||
{
|
||||
$this->tokens = \json_decode($this->request(
|
||||
'POST',
|
||||
'https://gitlab.com/oauth/token?' . \http_build_query([
|
||||
$this->getEndpoint() . '/oauth/token?' . \http_build_query([
|
||||
'refresh_token' => $refreshToken,
|
||||
'client_id' => $this->appID,
|
||||
'client_secret' => $this->appSecret,
|
||||
'client_secret' => $this->getAppSecret()['clientSecret'],
|
||||
'grant_type' => 'refresh_token'
|
||||
])
|
||||
), true);
|
||||
@@ -163,10 +163,39 @@ class Gitlab extends OAuth2
|
||||
protected function getUser(string $accessToken): array
|
||||
{
|
||||
if (empty($this->user)) {
|
||||
$user = $this->request('GET', 'https://gitlab.com/api/v4/user?access_token=' . \urlencode($accessToken));
|
||||
$user = $this->request('GET', $this->getEndpoint() . '/api/v4/user?access_token=' . \urlencode($accessToken));
|
||||
$this->user = \json_decode($user, true);
|
||||
}
|
||||
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode the JSON stored in appSecret
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getAppSecret(): array
|
||||
{
|
||||
try {
|
||||
$secret = \json_decode($this->appSecret, true, 512, JSON_THROW_ON_ERROR);
|
||||
} catch (\Throwable $th) {
|
||||
throw new \Exception('Invalid secret');
|
||||
}
|
||||
return $secret;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extracts the Tenant Id from the JSON stored in appSecret. Defaults to 'common' as a fallback
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getEndpoint(): string
|
||||
{
|
||||
$defaultEndpoint = 'https://gitlab.com';
|
||||
$secret = $this->getAppSecret();
|
||||
$endpoint = $secret['endpoint'] ?? $defaultEndpoint;
|
||||
return empty($endpoint) ? $defaultEndpoint : $endpoint;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,8 +9,8 @@ use Appwrite\Auth\OAuth2;
|
||||
|
||||
class Tradeshift extends OAuth2
|
||||
{
|
||||
const TRADESHIFT_SANDBOX_API_DOMAIN = 'api-sandbox.tradeshift.com';
|
||||
const TRADESHIFT_API_DOMAIN = 'api.tradeshift.com';
|
||||
public const TRADESHIFT_SANDBOX_API_DOMAIN = 'api-sandbox.tradeshift.com';
|
||||
public const TRADESHIFT_API_DOMAIN = 'api.tradeshift.com';
|
||||
|
||||
private array $apiDomain = [
|
||||
'sandbox' => self::TRADESHIFT_SANDBOX_API_DOMAIN,
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Auth;
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
|
||||
abstract class Phone
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected string $user;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected string $secret;
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
*/
|
||||
public function __construct(string $user, string $secret)
|
||||
{
|
||||
$this->user = $user;
|
||||
$this->secret = $secret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send Message to phone.
|
||||
* @param string $from
|
||||
* @param string $to
|
||||
* @param string $message
|
||||
* @return void
|
||||
*/
|
||||
abstract public function send(string $from, string $to, string $message): void;
|
||||
|
||||
/**
|
||||
* @param string $method
|
||||
* @param string $url
|
||||
* @param array $headers
|
||||
* @param string $payload
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function request(string $method, string $url, array $headers = [], ?string $payload = null, ?string $userpwd = null): string
|
||||
{
|
||||
$ch = \curl_init($url);
|
||||
|
||||
\curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
|
||||
\curl_setopt($ch, CURLOPT_HEADER, 0);
|
||||
\curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
|
||||
\curl_setopt($ch, CURLOPT_USERAGENT, 'Appwrite Phone Authentication');
|
||||
|
||||
if (!is_null($payload)) {
|
||||
\curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
|
||||
}
|
||||
|
||||
if (!is_null($userpwd)) {
|
||||
\curl_setopt($ch, CURLOPT_USERPWD, $userpwd);
|
||||
}
|
||||
|
||||
$headers[] = 'Content-length: ' . \strlen($payload);
|
||||
|
||||
\curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||
|
||||
$response = (string) \curl_exec($ch);
|
||||
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
|
||||
\curl_close($ch);
|
||||
|
||||
if ($code >= 400) {
|
||||
throw new Exception($response);
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate 6 random digits for phone verification.
|
||||
*
|
||||
* @param int $digits
|
||||
* @return string
|
||||
*/
|
||||
public function generateSecretDigits(int $digits = 6): string
|
||||
{
|
||||
return substr(str_shuffle("0123456789"), 0, $digits);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Auth\Phone;
|
||||
|
||||
use Appwrite\Auth\Phone;
|
||||
|
||||
class Mock extends Phone
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public static string $defaultDigits = '123456';
|
||||
|
||||
/**
|
||||
* @param string $from
|
||||
* @param string $to
|
||||
* @param string $message
|
||||
* @return void
|
||||
*/
|
||||
public function send(string $from, string $to, string $message): void
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $digits
|
||||
* @return string
|
||||
*/
|
||||
public function generateSecretDigits(int $digits = 6): string
|
||||
{
|
||||
return self::$defaultDigits;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Auth\Phone;
|
||||
|
||||
use Appwrite\Auth\Phone;
|
||||
|
||||
// Reference Material
|
||||
// https://www.twilio.com/docs/sms/api
|
||||
|
||||
class Telesign extends Phone
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private string $endpoint = 'https://rest-api.telesign.com/v1/messaging';
|
||||
|
||||
/**
|
||||
* @param string $from
|
||||
* @param string $to
|
||||
* @param string $message
|
||||
* @return void
|
||||
* @throws \Appwrite\Extend\Exception
|
||||
*/
|
||||
public function send(string $from, string $to, string $message): void
|
||||
{
|
||||
$to = ltrim($to, '+');
|
||||
|
||||
$this->request(
|
||||
method: 'POST',
|
||||
url: $this->endpoint,
|
||||
payload: \http_build_query([
|
||||
'message' => $message,
|
||||
'message_type' => 'otp',
|
||||
'phone_number' => $to
|
||||
]),
|
||||
userpwd: "{$this->user}:{$this->secret}"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Auth\Phone;
|
||||
|
||||
use Appwrite\Auth\Phone;
|
||||
|
||||
// Reference Material
|
||||
// https://www.textmagic.com/docs/api/start/
|
||||
|
||||
class TextMagic extends Phone
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private string $endpoint = 'https://rest.textmagic.com/api/v2';
|
||||
|
||||
/**
|
||||
* @param string $from
|
||||
* @param string $to
|
||||
* @param string $message
|
||||
* @return void
|
||||
*/
|
||||
public function send(string $from, string $to, string $message): void
|
||||
{
|
||||
$to = ltrim($to, '+');
|
||||
$from = ltrim($from, '+');
|
||||
|
||||
$this->request(
|
||||
method: 'POST',
|
||||
url: $this->endpoint . '/messages',
|
||||
payload: \http_build_query([
|
||||
'text' => $message,
|
||||
'from' => $from,
|
||||
'phones' => $to
|
||||
]),
|
||||
headers: [
|
||||
"X-TM-Username: {$this->user}",
|
||||
"X-TM-Key: {$this->secret}",
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Auth\Phone;
|
||||
|
||||
use Appwrite\Auth\Phone;
|
||||
|
||||
// Reference Material
|
||||
// https://www.twilio.com/docs/sms/api
|
||||
|
||||
class Twilio extends Phone
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private string $endpoint = 'https://api.twilio.com/2010-04-01';
|
||||
|
||||
/**
|
||||
* @param string $from
|
||||
* @param string $to
|
||||
* @param string $message
|
||||
* @return void
|
||||
*/
|
||||
public function send(string $from, string $to, string $message): void
|
||||
{
|
||||
$this->request(
|
||||
method: 'POST',
|
||||
url: "{$this->endpoint}/Accounts/{$this->user}/Messages.json",
|
||||
payload: \http_build_query([
|
||||
'Body' => $message,
|
||||
'From' => $from,
|
||||
'To' => $to
|
||||
]),
|
||||
userpwd: "{$this->user}:{$this->secret}"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Auth\Validator;
|
||||
|
||||
use Utopia\Validator;
|
||||
|
||||
/**
|
||||
* Phone.
|
||||
*
|
||||
* Validates a number for the E.164 format.
|
||||
*/
|
||||
class Phone extends Validator
|
||||
{
|
||||
/**
|
||||
* Get Description.
|
||||
*
|
||||
* Returns validator description
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
return "Phone number must start with a '+' can have a maximum of fifteen digits.";
|
||||
}
|
||||
|
||||
/**
|
||||
* Is valid.
|
||||
*
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isValid($value): bool
|
||||
{
|
||||
return is_string($value) && !!\preg_match('/^\+[1-9]\d{1,14}$/', $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is array
|
||||
*
|
||||
* Function will return true if object is array.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isArray(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Type
|
||||
*
|
||||
* Returns validator type.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return self::TYPE_STRING;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\DSN;
|
||||
|
||||
class DSN
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected string $scheme;
|
||||
|
||||
/**
|
||||
* @var ?string
|
||||
*/
|
||||
protected ?string $user;
|
||||
|
||||
/**
|
||||
* @var ?string
|
||||
*/
|
||||
protected ?string $password;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected string $host;
|
||||
|
||||
/**
|
||||
* @var ?string
|
||||
*/
|
||||
protected ?string $port;
|
||||
|
||||
/**
|
||||
* @var ?string
|
||||
*/
|
||||
protected ?string $database;
|
||||
|
||||
/**
|
||||
* @var ?string
|
||||
*/
|
||||
protected ?string $query;
|
||||
|
||||
/**
|
||||
* Construct
|
||||
*
|
||||
* Construct a new DSN object
|
||||
*
|
||||
* @param string $dsn
|
||||
*/
|
||||
public function __construct(string $dsn)
|
||||
{
|
||||
$parts = parse_url($dsn);
|
||||
|
||||
if (!$parts) {
|
||||
throw new \InvalidArgumentException("Unable to parse DSN: $dsn");
|
||||
}
|
||||
|
||||
$this->scheme = $parts['scheme'] ?? null;
|
||||
$this->user = $parts['user'] ?? null;
|
||||
$this->password = $parts['pass'] ?? null;
|
||||
$this->host = $parts['host'] ?? null;
|
||||
$this->port = $parts['port'] ?? null;
|
||||
$this->database = $parts['path'] ?? null;
|
||||
$this->query = $parts['query'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the scheme.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getScheme(): string
|
||||
{
|
||||
return $this->scheme;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the user.
|
||||
*
|
||||
* @return ?string
|
||||
*/
|
||||
public function getUser(): ?string
|
||||
{
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the password.
|
||||
*
|
||||
* @return ?string
|
||||
*/
|
||||
public function getPassword(): ?string
|
||||
{
|
||||
return $this->password;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the host
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getHost(): string
|
||||
{
|
||||
return $this->host;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the port
|
||||
*
|
||||
* @return ?string
|
||||
*/
|
||||
public function getPort(): ?string
|
||||
{
|
||||
return $this->port;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the database
|
||||
*
|
||||
* @return ?string
|
||||
*/
|
||||
public function getDatabase(): ?string
|
||||
{
|
||||
return ltrim($this->database, '/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the query string
|
||||
*
|
||||
* @return ?string
|
||||
*/
|
||||
public function getQuery(): ?string
|
||||
{
|
||||
return $this->query;
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ use Utopia\Database\Document;
|
||||
class Database extends Event
|
||||
{
|
||||
protected string $type = '';
|
||||
protected ?Document $database = null;
|
||||
protected ?Document $collection = null;
|
||||
protected ?Document $document = null;
|
||||
|
||||
@@ -38,6 +39,18 @@ class Database extends Event
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the database for this event
|
||||
*
|
||||
* @param Document $database
|
||||
* @return self
|
||||
*/
|
||||
public function setDatabase(Document $database): self
|
||||
{
|
||||
$this->database = $database;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the collection for this database event.
|
||||
*
|
||||
@@ -97,6 +110,7 @@ class Database extends Event
|
||||
'type' => $this->type,
|
||||
'collection' => $this->collection,
|
||||
'document' => $this->document,
|
||||
'database' => $this->database,
|
||||
'events' => Event::generateEvents($this->getEvent(), $this->getParams())
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -8,38 +8,41 @@ use Utopia\Database\Document;
|
||||
|
||||
class Event
|
||||
{
|
||||
const DATABASE_QUEUE_NAME = 'v1-database';
|
||||
const DATABASE_CLASS_NAME = 'DatabaseV1';
|
||||
public const DATABASE_QUEUE_NAME = 'v1-database';
|
||||
public const DATABASE_CLASS_NAME = 'DatabaseV1';
|
||||
|
||||
const DELETE_QUEUE_NAME = 'v1-deletes';
|
||||
const DELETE_CLASS_NAME = 'DeletesV1';
|
||||
public const DELETE_QUEUE_NAME = 'v1-deletes';
|
||||
public const DELETE_CLASS_NAME = 'DeletesV1';
|
||||
|
||||
const AUDITS_QUEUE_NAME = 'v1-audits';
|
||||
const AUDITS_CLASS_NAME = 'AuditsV1';
|
||||
public const AUDITS_QUEUE_NAME = 'v1-audits';
|
||||
public const AUDITS_CLASS_NAME = 'AuditsV1';
|
||||
|
||||
const MAILS_QUEUE_NAME = 'v1-mails';
|
||||
const MAILS_CLASS_NAME = 'MailsV1';
|
||||
public const MAILS_QUEUE_NAME = 'v1-mails';
|
||||
public const MAILS_CLASS_NAME = 'MailsV1';
|
||||
|
||||
const FUNCTIONS_QUEUE_NAME = 'v1-functions';
|
||||
const FUNCTIONS_CLASS_NAME = 'FunctionsV1';
|
||||
public const FUNCTIONS_QUEUE_NAME = 'v1-functions';
|
||||
public const FUNCTIONS_CLASS_NAME = 'FunctionsV1';
|
||||
|
||||
const WEBHOOK_QUEUE_NAME = 'v1-webhooks';
|
||||
const WEBHOOK_CLASS_NAME = 'WebhooksV1';
|
||||
public const WEBHOOK_QUEUE_NAME = 'v1-webhooks';
|
||||
public const WEBHOOK_CLASS_NAME = 'WebhooksV1';
|
||||
|
||||
const CERTIFICATES_QUEUE_NAME = 'v1-certificates';
|
||||
const CERTIFICATES_CLASS_NAME = 'CertificatesV1';
|
||||
public const CERTIFICATES_QUEUE_NAME = 'v1-certificates';
|
||||
public const CERTIFICATES_CLASS_NAME = 'CertificatesV1';
|
||||
|
||||
const BUILDS_QUEUE_NAME = 'v1-builds';
|
||||
const BUILDS_CLASS_NAME = 'BuildsV1';
|
||||
public const BUILDS_QUEUE_NAME = 'v1-builds';
|
||||
public const BUILDS_CLASS_NAME = 'BuildsV1';
|
||||
|
||||
public const MESSAGING_QUEUE_NAME = 'v1-messaging';
|
||||
public const MESSAGING_CLASS_NAME = 'MessagingV1';
|
||||
|
||||
protected string $queue = '';
|
||||
protected string $class = '';
|
||||
protected string $event = '';
|
||||
protected array $params = [];
|
||||
protected array $payload = [];
|
||||
protected array $context = [];
|
||||
protected ?Document $project = null;
|
||||
protected ?Document $user = null;
|
||||
protected ?Document $context = null;
|
||||
|
||||
/**
|
||||
* @param string $queue
|
||||
@@ -169,12 +172,13 @@ class Event
|
||||
/**
|
||||
* Set context for this event.
|
||||
*
|
||||
* @param string $key
|
||||
* @param Document $context
|
||||
* @return self
|
||||
*/
|
||||
public function setContext(Document $context): self
|
||||
public function setContext(string $key, Document $context): self
|
||||
{
|
||||
$this->context = $context;
|
||||
$this->context[$key] = $context;
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -182,11 +186,13 @@ class Event
|
||||
/**
|
||||
* Get context for this event.
|
||||
*
|
||||
* @param string $key
|
||||
*
|
||||
* @return null|Document
|
||||
*/
|
||||
public function getContext(): ?Document
|
||||
public function getContext(string $key): ?Document
|
||||
{
|
||||
return $this->context;
|
||||
return $this->context[$key] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -292,14 +298,28 @@ class Event
|
||||
$type = $parts[0] ?? false;
|
||||
$resource = $parts[1] ?? false;
|
||||
$hasSubResource = $count > 3 && \str_starts_with($parts[3], '[');
|
||||
$hasSubSubResource = $count > 5 && \str_starts_with($parts[5], '[') && $hasSubResource;
|
||||
|
||||
if ($hasSubResource) {
|
||||
$subType = $parts[2];
|
||||
$subResource = $parts[3];
|
||||
}
|
||||
|
||||
if ($hasSubSubResource) {
|
||||
$subSubType = $parts[4];
|
||||
$subSubResource = $parts[5];
|
||||
if ($count == 8) {
|
||||
$attribute = $parts[7];
|
||||
}
|
||||
}
|
||||
|
||||
if ($hasSubResource && !$hasSubSubResource) {
|
||||
if ($count === 6) {
|
||||
$attribute = $parts[5];
|
||||
}
|
||||
} else {
|
||||
}
|
||||
|
||||
if (!$hasSubResource) {
|
||||
if ($count === 4) {
|
||||
$attribute = $parts[3];
|
||||
}
|
||||
@@ -307,18 +327,25 @@ class Event
|
||||
|
||||
$subType ??= false;
|
||||
$subResource ??= false;
|
||||
$subSubType ??= false;
|
||||
$subSubResource ??= false;
|
||||
$attribute ??= false;
|
||||
$action = match (true) {
|
||||
!$hasSubResource && $count > 2 => $parts[2],
|
||||
$hasSubSubResource => $parts[6] ?? false,
|
||||
$hasSubResource && $count > 4 => $parts[4],
|
||||
default => false
|
||||
};
|
||||
|
||||
|
||||
|
||||
return [
|
||||
'type' => $type,
|
||||
'resource' => $resource,
|
||||
'subType' => $subType,
|
||||
'subResource' => $subResource,
|
||||
'subSubType' => $subSubType,
|
||||
'subSubResource' => $subSubResource,
|
||||
'action' => $action,
|
||||
'attribute' => $attribute,
|
||||
];
|
||||
@@ -332,7 +359,7 @@ class Event
|
||||
* @return array
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
static function generateEvents(string $pattern, array $params = []): array
|
||||
public static function generateEvents(string $pattern, array $params = []): array
|
||||
{
|
||||
// $params = \array_filter($params, fn($param) => !\is_array($param));
|
||||
$paramKeys = \array_keys($params);
|
||||
@@ -345,6 +372,8 @@ class Event
|
||||
$resource = $parsed['resource'];
|
||||
$subType = $parsed['subType'];
|
||||
$subResource = $parsed['subResource'];
|
||||
$subSubType = $parsed['subSubType'];
|
||||
$subSubResource = $parsed['subSubResource'];
|
||||
$action = $parsed['action'];
|
||||
$attribute = $parsed['attribute'];
|
||||
|
||||
@@ -356,11 +385,21 @@ class Event
|
||||
throw new InvalidArgumentException("{$subResource} is missing from the params.");
|
||||
}
|
||||
|
||||
if ($subSubResource && !\in_array(\trim($subSubResource, "\[\]"), $paramKeys)) {
|
||||
throw new InvalidArgumentException("{$subSubResource} is missing from the params.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Create all possible patterns including placeholders.
|
||||
*/
|
||||
if ($action) {
|
||||
if ($subResource) {
|
||||
if ($subSubResource) {
|
||||
if ($attribute) {
|
||||
$patterns[] = \implode('.', [$type, $resource, $subType, $subResource, $subSubType, $subSubResource, $action, $attribute]);
|
||||
}
|
||||
$patterns[] = \implode('.', [$type, $resource, $subType, $subResource, $subSubType, $subSubResource, $action]);
|
||||
$patterns[] = \implode('.', [$type, $resource, $subType, $subResource, $subSubType, $subSubResource]);
|
||||
} elseif ($subResource) {
|
||||
if ($attribute) {
|
||||
$patterns[] = \implode('.', [$type, $resource, $subType, $subResource, $action, $attribute]);
|
||||
}
|
||||
@@ -373,6 +412,9 @@ class Event
|
||||
$patterns[] = \implode('.', [$type, $resource, $action, $attribute]);
|
||||
}
|
||||
}
|
||||
if ($subSubResource) {
|
||||
$patterns[] = \implode('.', [$type, $resource, $subType, $subResource, $subSubType, $subSubResource]);
|
||||
}
|
||||
if ($subResource) {
|
||||
$patterns[] = \implode('.', [$type, $resource, $subType, $subResource]);
|
||||
}
|
||||
@@ -392,12 +434,24 @@ class Event
|
||||
$events[] = \str_replace($paramKeys, '*', $eventPattern);
|
||||
foreach ($paramKeys as $key) {
|
||||
foreach ($paramKeys as $current) {
|
||||
if ($current === $key) {
|
||||
continue;
|
||||
if ($subSubResource) {
|
||||
foreach ($paramKeys as $subCurrent) {
|
||||
if ($subCurrent === $current || $subCurrent === $key) {
|
||||
continue;
|
||||
}
|
||||
$filtered1 = \array_filter($paramKeys, fn(string $k) => $k === $subCurrent);
|
||||
$events[] = \str_replace($paramKeys, $paramValues, \str_replace($filtered1, '*', $eventPattern));
|
||||
$filtered2 = \array_filter($paramKeys, fn(string $k) => $k === $current);
|
||||
$events[] = \str_replace($paramKeys, $paramValues, \str_replace($filtered2, '*', \str_replace($filtered1, '*', $eventPattern)));
|
||||
$events[] = \str_replace($paramKeys, $paramValues, \str_replace($filtered2, '*', $eventPattern));
|
||||
}
|
||||
} else {
|
||||
if ($current === $key) {
|
||||
continue;
|
||||
}
|
||||
$filtered = \array_filter($paramKeys, fn(string $k) => $k === $current);
|
||||
$events[] = \str_replace($paramKeys, $paramValues, \str_replace($filtered, '*', $eventPattern));
|
||||
}
|
||||
|
||||
$filtered = \array_filter($paramKeys, fn(string $k) => $k === $current);
|
||||
$events[] = \str_replace($paramKeys, $paramValues, \str_replace($filtered, '*', $eventPattern));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -408,6 +462,9 @@ class Event
|
||||
$events = \array_map(fn (string $event) => \str_replace(['[', ']'], '', $event), $events);
|
||||
$events = \array_unique($events);
|
||||
|
||||
return $events;
|
||||
/**
|
||||
* Force a non-assoc array.
|
||||
*/
|
||||
return \array_values($events);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Event;
|
||||
|
||||
use Resque;
|
||||
|
||||
class Phone extends Event
|
||||
{
|
||||
protected string $recipient = '';
|
||||
protected string $message = '';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct(Event::MESSAGING_QUEUE_NAME, Event::MESSAGING_CLASS_NAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets recipient for the messaging event.
|
||||
*
|
||||
* @param string $recipient
|
||||
* @return self
|
||||
*/
|
||||
public function setRecipient(string $recipient): self
|
||||
{
|
||||
$this->recipient = $recipient;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns set recipient for this messaging event.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRecipient(): string
|
||||
{
|
||||
return $this->recipient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets url for the messaging event.
|
||||
*
|
||||
* @param string $message
|
||||
* @return self
|
||||
*/
|
||||
public function setMessage(string $message): self
|
||||
{
|
||||
$this->message = $message;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns set url for the messaging event.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getMessage(): string
|
||||
{
|
||||
return $this->message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the event and sends it to the messaging worker.
|
||||
*
|
||||
* @return string|bool
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function trigger(): string|bool
|
||||
{
|
||||
return Resque::enqueue($this->queue, $this->class, [
|
||||
'project' => $this->project,
|
||||
'user' => $this->user,
|
||||
'payload' => $this->payload,
|
||||
'recipient' => $this->recipient,
|
||||
'message' => $this->message,
|
||||
'events' => Event::generateEvents($this->getEvent(), $this->getParams())
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,7 @@ class Event extends Validator
|
||||
$parts = \explode('.', $value);
|
||||
$count = \count($parts);
|
||||
|
||||
if ($count < 2 || $count > 6) {
|
||||
if ($count < 2 || $count > 7) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ class Event extends Validator
|
||||
$type = $parts[0] ?? false;
|
||||
$resource = $parts[1] ?? false;
|
||||
$hasSubResource = $count > 3 && ($events[$type]['$resource'] ?? false) && ($events[$type][$parts[2]]['$resource'] ?? false);
|
||||
$hasSubSubResource = $count > 5 && $hasSubResource && ($events[$type][$parts[2]][$parts[4]]['$resource'] ?? false);
|
||||
|
||||
if (!$type || !$resource) {
|
||||
return false;
|
||||
@@ -50,21 +51,37 @@ class Event extends Validator
|
||||
if ($hasSubResource) {
|
||||
$subType = $parts[2];
|
||||
$subResource = $parts[3];
|
||||
}
|
||||
|
||||
if ($hasSubSubResource) {
|
||||
$subSubType = $parts[4];
|
||||
$subSubResource = $parts[5];
|
||||
if ($count === 8) {
|
||||
$attribute = $parts[7];
|
||||
}
|
||||
}
|
||||
|
||||
if ($hasSubResource && !$hasSubSubResource) {
|
||||
if ($count === 6) {
|
||||
$attribute = $parts[5];
|
||||
}
|
||||
} else {
|
||||
}
|
||||
|
||||
if (!$hasSubResource) {
|
||||
if ($count === 4) {
|
||||
$attribute = $parts[3];
|
||||
}
|
||||
}
|
||||
|
||||
$subSubType ??= false;
|
||||
$subSubResource ??= false;
|
||||
$subType ??= false;
|
||||
$subResource ??= false;
|
||||
$attribute ??= false;
|
||||
|
||||
$action = match (true) {
|
||||
!$hasSubResource && $count > 2 => $parts[2],
|
||||
$hasSubSubResource => $parts[6] ?? false,
|
||||
$hasSubResource && $count > 4 => $parts[4],
|
||||
default => false
|
||||
};
|
||||
|
||||
@@ -32,136 +32,143 @@ class Exception extends \Exception
|
||||
*/
|
||||
|
||||
/** General */
|
||||
const GENERAL_UNKNOWN = 'general_unknown';
|
||||
const GENERAL_MOCK = 'general_mock';
|
||||
const GENERAL_ACCESS_FORBIDDEN = 'general_access_forbidden';
|
||||
const GENERAL_UNKNOWN_ORIGIN = 'general_unknown_origin';
|
||||
const GENERAL_SERVICE_DISABLED = 'general_service_disabled';
|
||||
const GENERAL_UNAUTHORIZED_SCOPE = 'general_unauthorized_scope';
|
||||
const GENERAL_RATE_LIMIT_EXCEEDED = 'general_rate_limit_exceeded';
|
||||
const GENERAL_SMTP_DISABLED = 'general_smtp_disabled';
|
||||
const GENERAL_ARGUMENT_INVALID = 'general_argument_invalid';
|
||||
const GENERAL_QUERY_LIMIT_EXCEEDED = 'general_query_limit_exceeded';
|
||||
const GENERAL_QUERY_INVALID = 'general_query_invalid';
|
||||
const GENERAL_ROUTE_NOT_FOUND = 'general_route_not_found';
|
||||
const GENERAL_CURSOR_NOT_FOUND = 'general_cursor_not_found';
|
||||
const GENERAL_SERVER_ERROR = 'general_server_error';
|
||||
const GENERAL_PROTOCOL_UNSUPPORTED = 'general_protocol_unsupported';
|
||||
public const GENERAL_UNKNOWN = 'general_unknown';
|
||||
public const GENERAL_MOCK = 'general_mock';
|
||||
public const GENERAL_ACCESS_FORBIDDEN = 'general_access_forbidden';
|
||||
public const GENERAL_UNKNOWN_ORIGIN = 'general_unknown_origin';
|
||||
public const GENERAL_SERVICE_DISABLED = 'general_service_disabled';
|
||||
public const GENERAL_UNAUTHORIZED_SCOPE = 'general_unauthorized_scope';
|
||||
public const GENERAL_RATE_LIMIT_EXCEEDED = 'general_rate_limit_exceeded';
|
||||
public const GENERAL_SMTP_DISABLED = 'general_smtp_disabled';
|
||||
public const GENERAL_PHONE_DISABLED = 'general_phone_disabled';
|
||||
public const GENERAL_ARGUMENT_INVALID = 'general_argument_invalid';
|
||||
public const GENERAL_QUERY_LIMIT_EXCEEDED = 'general_query_limit_exceeded';
|
||||
public const GENERAL_QUERY_INVALID = 'general_query_invalid';
|
||||
public const GENERAL_ROUTE_NOT_FOUND = 'general_route_not_found';
|
||||
public const GENERAL_CURSOR_NOT_FOUND = 'general_cursor_not_found';
|
||||
public const GENERAL_SERVER_ERROR = 'general_server_error';
|
||||
public const GENERAL_PROTOCOL_UNSUPPORTED = 'general_protocol_unsupported';
|
||||
|
||||
/** Users */
|
||||
const USER_COUNT_EXCEEDED = 'user_count_exceeded';
|
||||
const USER_JWT_INVALID = 'user_jwt_invalid';
|
||||
const USER_ALREADY_EXISTS = 'user_already_exists';
|
||||
const USER_BLOCKED = 'user_blocked';
|
||||
const USER_INVALID_TOKEN = 'user_invalid_token';
|
||||
const USER_PASSWORD_RESET_REQUIRED = 'user_password_reset_required';
|
||||
const USER_EMAIL_NOT_WHITELISTED = 'user_email_not_whitelisted';
|
||||
const USER_IP_NOT_WHITELISTED = 'user_ip_not_whitelisted';
|
||||
const USER_INVALID_CREDENTIALS = 'user_invalid_credentials';
|
||||
const USER_ANONYMOUS_CONSOLE_PROHIBITED = 'user_anonymous_console_prohibited';
|
||||
const USER_SESSION_ALREADY_EXISTS = 'user_session_already_exists';
|
||||
const USER_NOT_FOUND = 'user_not_found';
|
||||
const USER_EMAIL_ALREADY_EXISTS = 'user_email_already_exists';
|
||||
const USER_PASSWORD_MISMATCH = 'user_password_mismatch';
|
||||
const USER_SESSION_NOT_FOUND = 'user_session_not_found';
|
||||
const USER_UNAUTHORIZED = 'user_unauthorized';
|
||||
const USER_AUTH_METHOD_UNSUPPORTED = 'user_auth_method_unsupported';
|
||||
public const USER_COUNT_EXCEEDED = 'user_count_exceeded';
|
||||
public const USER_JWT_INVALID = 'user_jwt_invalid';
|
||||
public const USER_ALREADY_EXISTS = 'user_already_exists';
|
||||
public const USER_BLOCKED = 'user_blocked';
|
||||
public const USER_INVALID_TOKEN = 'user_invalid_token';
|
||||
public const USER_PASSWORD_RESET_REQUIRED = 'user_password_reset_required';
|
||||
public const USER_EMAIL_NOT_WHITELISTED = 'user_email_not_whitelisted';
|
||||
public const USER_IP_NOT_WHITELISTED = 'user_ip_not_whitelisted';
|
||||
public const USER_INVALID_CREDENTIALS = 'user_invalid_credentials';
|
||||
public const USER_ANONYMOUS_CONSOLE_PROHIBITED = 'user_anonymous_console_prohibited';
|
||||
public const USER_SESSION_ALREADY_EXISTS = 'user_session_already_exists';
|
||||
public const USER_NOT_FOUND = 'user_not_found';
|
||||
public const USER_EMAIL_ALREADY_EXISTS = 'user_email_already_exists';
|
||||
public const USER_PASSWORD_MISMATCH = 'user_password_mismatch';
|
||||
public const USER_SESSION_NOT_FOUND = 'user_session_not_found';
|
||||
public const USER_UNAUTHORIZED = 'user_unauthorized';
|
||||
public const USER_AUTH_METHOD_UNSUPPORTED = 'user_auth_method_unsupported';
|
||||
public const USER_PHONE_ALREADY_EXISTS = 'user_phone_already_exists';
|
||||
public const USER_PHONE_NOT_FOUND = 'user_phone_not_found';
|
||||
|
||||
/** Teams */
|
||||
const TEAM_NOT_FOUND = 'team_not_found';
|
||||
const TEAM_INVITE_ALREADY_EXISTS = 'team_invite_already_exists';
|
||||
const TEAM_INVITE_NOT_FOUND = 'team_invite_not_found';
|
||||
const TEAM_INVALID_SECRET = 'team_invalid_secret';
|
||||
const TEAM_MEMBERSHIP_MISMATCH = 'team_membership_mismatch';
|
||||
const TEAM_INVITE_MISMATCH = 'team_invite_mismatch';
|
||||
public const TEAM_NOT_FOUND = 'team_not_found';
|
||||
public const TEAM_INVITE_ALREADY_EXISTS = 'team_invite_already_exists';
|
||||
public const TEAM_INVITE_NOT_FOUND = 'team_invite_not_found';
|
||||
public const TEAM_INVALID_SECRET = 'team_invalid_secret';
|
||||
public const TEAM_MEMBERSHIP_MISMATCH = 'team_membership_mismatch';
|
||||
public const TEAM_INVITE_MISMATCH = 'team_invite_mismatch';
|
||||
|
||||
/** Membership */
|
||||
const MEMBERSHIP_NOT_FOUND = 'membership_not_found';
|
||||
public const MEMBERSHIP_NOT_FOUND = 'membership_not_found';
|
||||
|
||||
/** Avatars */
|
||||
const AVATAR_SET_NOT_FOUND = 'avatar_set_not_found';
|
||||
const AVATAR_NOT_FOUND = 'avatar_not_found';
|
||||
const AVATAR_IMAGE_NOT_FOUND = 'avatar_image_not_found';
|
||||
const AVATAR_REMOTE_URL_FAILED = 'avatar_remote_url_failed';
|
||||
const AVATAR_ICON_NOT_FOUND = 'avatar_icon_not_found';
|
||||
public const AVATAR_SET_NOT_FOUND = 'avatar_set_not_found';
|
||||
public const AVATAR_NOT_FOUND = 'avatar_not_found';
|
||||
public const AVATAR_IMAGE_NOT_FOUND = 'avatar_image_not_found';
|
||||
public const AVATAR_REMOTE_URL_FAILED = 'avatar_remote_url_failed';
|
||||
public const AVATAR_ICON_NOT_FOUND = 'avatar_icon_not_found';
|
||||
|
||||
/** Storage */
|
||||
const STORAGE_FILE_NOT_FOUND = 'storage_file_not_found';
|
||||
const STORAGE_DEVICE_NOT_FOUND = 'storage_device_not_found';
|
||||
const STORAGE_FILE_EMPTY = 'storage_file_empty';
|
||||
const STORAGE_FILE_TYPE_UNSUPPORTED = 'storage_file_type_unsupported';
|
||||
const STORAGE_INVALID_FILE_SIZE = 'storage_invalid_file_size';
|
||||
const STORAGE_INVALID_FILE = 'storage_invalid_file';
|
||||
const STORAGE_BUCKET_ALREADY_EXISTS = 'storage_bucket_already_exists';
|
||||
const STORAGE_BUCKET_NOT_FOUND = 'storage_bucket_not_found';
|
||||
const STORAGE_INVALID_CONTENT_RANGE = 'storage_invalid_content_range';
|
||||
const STORAGE_INVALID_RANGE = 'storage_invalid_range';
|
||||
public const STORAGE_FILE_NOT_FOUND = 'storage_file_not_found';
|
||||
public const STORAGE_DEVICE_NOT_FOUND = 'storage_device_not_found';
|
||||
public const STORAGE_FILE_EMPTY = 'storage_file_empty';
|
||||
public const STORAGE_FILE_TYPE_UNSUPPORTED = 'storage_file_type_unsupported';
|
||||
public const STORAGE_INVALID_FILE_SIZE = 'storage_invalid_file_size';
|
||||
public const STORAGE_INVALID_FILE = 'storage_invalid_file';
|
||||
public const STORAGE_BUCKET_ALREADY_EXISTS = 'storage_bucket_already_exists';
|
||||
public const STORAGE_BUCKET_NOT_FOUND = 'storage_bucket_not_found';
|
||||
public const STORAGE_INVALID_CONTENT_RANGE = 'storage_invalid_content_range';
|
||||
public const STORAGE_INVALID_RANGE = 'storage_invalid_range';
|
||||
|
||||
/** Functions */
|
||||
const FUNCTION_NOT_FOUND = 'function_not_found';
|
||||
const FUNCTION_RUNTIME_UNSUPPORTED = 'function_runtime_unsupported';
|
||||
public const FUNCTION_NOT_FOUND = 'function_not_found';
|
||||
public const FUNCTION_RUNTIME_UNSUPPORTED = 'function_runtime_unsupported';
|
||||
|
||||
/** Deployments */
|
||||
const DEPLOYMENT_NOT_FOUND = 'deployment_not_found';
|
||||
public const DEPLOYMENT_NOT_FOUND = 'deployment_not_found';
|
||||
|
||||
/** Builds */
|
||||
const BUILD_NOT_FOUND = 'build_not_found';
|
||||
const BUILD_NOT_READY = 'build_not_ready';
|
||||
const BUILD_IN_PROGRESS = 'build_in_progress';
|
||||
public const BUILD_NOT_FOUND = 'build_not_found';
|
||||
public const BUILD_NOT_READY = 'build_not_ready';
|
||||
public const BUILD_IN_PROGRESS = 'build_in_progress';
|
||||
|
||||
/** Execution */
|
||||
const EXECUTION_NOT_FOUND = 'execution_not_found';
|
||||
public const EXECUTION_NOT_FOUND = 'execution_not_found';
|
||||
|
||||
/** Databases */
|
||||
public const DATABASE_NOT_FOUND = 'database_not_found';
|
||||
public const DATABASE_ALREADY_EXISTS = 'database_already_exists';
|
||||
|
||||
/** Collections */
|
||||
const COLLECTION_NOT_FOUND = 'collection_not_found';
|
||||
const COLLECTION_ALREADY_EXISTS = 'collection_already_exists';
|
||||
const COLLECTION_LIMIT_EXCEEDED = 'collection_limit_exceeded';
|
||||
public const COLLECTION_NOT_FOUND = 'collection_not_found';
|
||||
public const COLLECTION_ALREADY_EXISTS = 'collection_already_exists';
|
||||
public const COLLECTION_LIMIT_EXCEEDED = 'collection_limit_exceeded';
|
||||
|
||||
/** Documents */
|
||||
const DOCUMENT_NOT_FOUND = 'document_not_found';
|
||||
const DOCUMENT_INVALID_STRUCTURE = 'document_invalid_structure';
|
||||
const DOCUMENT_MISSING_PAYLOAD = 'document_missing_payload';
|
||||
const DOCUMENT_ALREADY_EXISTS = 'document_already_exists';
|
||||
public const DOCUMENT_NOT_FOUND = 'document_not_found';
|
||||
public const DOCUMENT_INVALID_STRUCTURE = 'document_invalid_structure';
|
||||
public const DOCUMENT_MISSING_PAYLOAD = 'document_missing_payload';
|
||||
public const DOCUMENT_ALREADY_EXISTS = 'document_already_exists';
|
||||
|
||||
/** Attribute */
|
||||
const ATTRIBUTE_NOT_FOUND = 'attribute_not_found';
|
||||
const ATTRIBUTE_UNKNOWN = 'attribute_unknown';
|
||||
const ATTRIBUTE_NOT_AVAILABLE = 'attribute_not_available';
|
||||
const ATTRIBUTE_FORMAT_UNSUPPORTED = 'attribute_format_unsupported';
|
||||
const ATTRIBUTE_DEFAULT_UNSUPPORTED = 'attribute_default_unsupported';
|
||||
const ATTRIBUTE_ALREADY_EXISTS = 'attribute_already_exists';
|
||||
const ATTRIBUTE_LIMIT_EXCEEDED = 'attribute_limit_exceeded';
|
||||
const ATTRIBUTE_VALUE_INVALID = 'attribute_value_invalid';
|
||||
public const ATTRIBUTE_NOT_FOUND = 'attribute_not_found';
|
||||
public const ATTRIBUTE_UNKNOWN = 'attribute_unknown';
|
||||
public const ATTRIBUTE_NOT_AVAILABLE = 'attribute_not_available';
|
||||
public const ATTRIBUTE_FORMAT_UNSUPPORTED = 'attribute_format_unsupported';
|
||||
public const ATTRIBUTE_DEFAULT_UNSUPPORTED = 'attribute_default_unsupported';
|
||||
public const ATTRIBUTE_ALREADY_EXISTS = 'attribute_already_exists';
|
||||
public const ATTRIBUTE_LIMIT_EXCEEDED = 'attribute_limit_exceeded';
|
||||
public const ATTRIBUTE_VALUE_INVALID = 'attribute_value_invalid';
|
||||
|
||||
/** Indexes */
|
||||
const INDEX_NOT_FOUND = 'index_not_found';
|
||||
const INDEX_LIMIT_EXCEEDED = 'index_limit_exceeded';
|
||||
const INDEX_ALREADY_EXISTS = 'index_already_exists';
|
||||
public const INDEX_NOT_FOUND = 'index_not_found';
|
||||
public const INDEX_LIMIT_EXCEEDED = 'index_limit_exceeded';
|
||||
public const INDEX_ALREADY_EXISTS = 'index_already_exists';
|
||||
|
||||
/** Projects */
|
||||
const PROJECT_NOT_FOUND = 'project_not_found';
|
||||
const PROJECT_UNKNOWN = 'project_unknown';
|
||||
const PROJECT_PROVIDER_DISABLED = 'project_provider_disabled';
|
||||
const PROJECT_PROVIDER_UNSUPPORTED = 'project_provider_unsupported';
|
||||
const PROJECT_INVALID_SUCCESS_URL = 'project_invalid_success_url';
|
||||
const PROJECT_INVALID_FAILURE_URL = 'project_invalid_failure_url';
|
||||
const PROJECT_MISSING_USER_ID = 'project_missing_user_id';
|
||||
const PROJECT_RESERVED_PROJECT = 'project_reserved_project';
|
||||
const PROJECT_KEY_EXPIRED = 'project_key_expired';
|
||||
public const PROJECT_NOT_FOUND = 'project_not_found';
|
||||
public const PROJECT_UNKNOWN = 'project_unknown';
|
||||
public const PROJECT_PROVIDER_DISABLED = 'project_provider_disabled';
|
||||
public const PROJECT_PROVIDER_UNSUPPORTED = 'project_provider_unsupported';
|
||||
public const PROJECT_INVALID_SUCCESS_URL = 'project_invalid_success_url';
|
||||
public const PROJECT_INVALID_FAILURE_URL = 'project_invalid_failure_url';
|
||||
public const PROJECT_MISSING_USER_ID = 'project_missing_user_id';
|
||||
public const PROJECT_RESERVED_PROJECT = 'project_reserved_project';
|
||||
public const PROJECT_KEY_EXPIRED = 'project_key_expired';
|
||||
|
||||
/** Webhooks */
|
||||
const WEBHOOK_NOT_FOUND = 'webhook_not_found';
|
||||
public const WEBHOOK_NOT_FOUND = 'webhook_not_found';
|
||||
|
||||
/** Keys */
|
||||
const KEY_NOT_FOUND = 'key_not_found';
|
||||
public const KEY_NOT_FOUND = 'key_not_found';
|
||||
|
||||
/** Platform */
|
||||
const PLATFORM_NOT_FOUND = 'platform_not_found';
|
||||
public const PLATFORM_NOT_FOUND = 'platform_not_found';
|
||||
|
||||
/** Domain */
|
||||
const DOMAIN_NOT_FOUND = 'domain_not_found';
|
||||
const DOMAIN_ALREADY_EXISTS = 'domain_already_exists';
|
||||
const DOMAIN_VERIFICATION_FAILED = 'domain_verification_failed';
|
||||
public const DOMAIN_NOT_FOUND = 'domain_not_found';
|
||||
public const DOMAIN_ALREADY_EXISTS = 'domain_already_exists';
|
||||
public const DOMAIN_VERIFICATION_FAILED = 'domain_verification_failed';
|
||||
|
||||
|
||||
private $type = '';
|
||||
|
||||
@@ -242,7 +242,7 @@ class Realtime extends Adapter
|
||||
* @param Document|null $project
|
||||
* @return array
|
||||
*/
|
||||
public static function fromPayload(string $event, Document $payload, Document $project = null, Document $collection = null, Document $bucket = null): array
|
||||
public static function fromPayload(string $event, Document $payload, Document $project = null, Document $database = null, Document $collection = null, Document $bucket = null): array
|
||||
{
|
||||
$channels = [];
|
||||
$roles = [];
|
||||
@@ -271,19 +271,22 @@ class Realtime extends Adapter
|
||||
$roles = ['team:' . $parts[1]];
|
||||
}
|
||||
break;
|
||||
case 'collections':
|
||||
if (in_array($parts[2], ['attributes', 'indexes'])) {
|
||||
case 'databases':
|
||||
if (in_array($parts[4] ?? [], ['attributes', 'indexes'])) {
|
||||
$channels[] = 'console';
|
||||
$projectId = 'console';
|
||||
$roles = ['team:' . $project->getAttribute('teamId')];
|
||||
} elseif ($parts[2] === 'documents') {
|
||||
} elseif (($parts[4] ?? '') === 'documents') {
|
||||
if ($database->isEmpty()) {
|
||||
throw new \Exception('Database needs to be passed to Realtime for Document events in the Database.');
|
||||
}
|
||||
if ($collection->isEmpty()) {
|
||||
throw new \Exception('Collection needs to be passed to Realtime for Document events in the Database.');
|
||||
}
|
||||
|
||||
$channels[] = 'documents';
|
||||
$channels[] = 'collections.' . $payload->getCollection() . '.documents';
|
||||
$channels[] = 'collections.' . $payload->getCollection() . '.documents.' . $payload->getId();
|
||||
$channels[] = 'databases.' . $database->getId() . '.collections.' . $payload->getCollection() . '.documents';
|
||||
$channels[] = 'databases.' . $database->getId() . '.collections.' . $payload->getCollection() . '.documents.' . $payload->getId();
|
||||
|
||||
$roles = ($collection->getAttribute('permission') === 'collection') ? $collection->getRead() : $payload->getRead();
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ abstract class Migration
|
||||
'0.14.0' => 'V13',
|
||||
'0.14.1' => 'V13',
|
||||
'0.14.2' => 'V13',
|
||||
'0.15.0' => 'V14'
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -103,8 +104,9 @@ abstract class Migration
|
||||
|
||||
foreach ($this->collections as $collection) {
|
||||
if ($collection['$collection'] !== Database::METADATA) {
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
|
||||
$sum = 0;
|
||||
$nextDocument = null;
|
||||
$collectionCount = $this->projectDB->count($collection['$id']);
|
||||
@@ -127,7 +129,7 @@ abstract class Migration
|
||||
$old = $document->getArrayCopy();
|
||||
$new = call_user_func($callback, $document);
|
||||
|
||||
if (!self::hasDifference($new->getArrayCopy(), $old)) {
|
||||
if (is_null($new) || !self::hasDifference($new->getArrayCopy(), $old)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -227,6 +229,87 @@ abstract class Migration
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates attribute from collections.php
|
||||
*
|
||||
* @param \Utopia\Database\Database $database
|
||||
* @param string $collectionId
|
||||
* @param string $attributeId
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
* @throws \Utopia\Database\Exception\Duplicate
|
||||
* @throws \Utopia\Database\Exception\Limit
|
||||
*/
|
||||
public function createAttributeFromCollection(Database $database, string $collectionId, string $attributeId, string $from = null): void
|
||||
{
|
||||
$from ??= $collectionId;
|
||||
$collection = Config::getParam('collections', [])[$from] ?? null;
|
||||
if (is_null($collection)) {
|
||||
throw new Exception("Collection {$collectionId} not found");
|
||||
}
|
||||
$attributes = $collection['attributes'];
|
||||
|
||||
$attributeKey = array_search($attributeId, array_column($attributes, '$id'));
|
||||
|
||||
if ($attributeKey === false) {
|
||||
throw new Exception("Attribute {$attributeId} not found");
|
||||
}
|
||||
|
||||
$attribute = $attributes[$attributeKey];
|
||||
|
||||
$database->createAttribute(
|
||||
collection: $collectionId,
|
||||
id: $attributeId,
|
||||
type: $attribute['type'],
|
||||
size: $attribute['size'],
|
||||
required: $attribute['required'] ?? false,
|
||||
default: $attribute['default'] ?? null,
|
||||
signed: $attribute['signed'] ?? false,
|
||||
array: $attribute['array'] ?? false,
|
||||
format: $attribute['format'] ?? '',
|
||||
formatOptions: $attribute['formatOptions'] ?? [],
|
||||
filters: $attribute['filters'] ?? [],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates index from collections.php
|
||||
*
|
||||
* @param \Utopia\Database\Database $database
|
||||
* @param string $collectionId
|
||||
* @param string $indexId
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
* @throws \Utopia\Database\Exception\Duplicate
|
||||
* @throws \Utopia\Database\Exception\Limit
|
||||
*/
|
||||
public function createIndexFromCollection(Database $database, string $collectionId, string $indexId): void
|
||||
{
|
||||
$collection = Config::getParam('collections', [])[$collectionId] ?? null;
|
||||
|
||||
if (is_null($collection)) {
|
||||
throw new Exception("Collection {$collectionId} not found");
|
||||
}
|
||||
$indexes = $collection['indexes'];
|
||||
|
||||
$indexKey = array_search($indexId, array_column($indexes, '$id'));
|
||||
|
||||
if ($indexKey === false) {
|
||||
throw new Exception("Attribute {$indexId} not found");
|
||||
}
|
||||
|
||||
$index = $indexes[$indexKey];
|
||||
|
||||
$database->createIndex(
|
||||
collection: $collectionId,
|
||||
id: $indexId,
|
||||
type: $index['type'],
|
||||
attributes: $index['attributes'],
|
||||
lengths: $index['lengths'] ?? [],
|
||||
orders: $index['orders'] ?? []
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes migration for set project.
|
||||
*/
|
||||
|
||||
@@ -319,7 +319,7 @@ class V13 extends Migration
|
||||
return 'buckets.*.' . implode('.', $parts);
|
||||
case 'files':
|
||||
return 'buckets.*.' . $second . '.*.' . implode('.', $parts);
|
||||
}
|
||||
} // intentional fallthrough
|
||||
case 'database':
|
||||
$second = array_shift($parts);
|
||||
switch ($second) {
|
||||
|
||||
@@ -0,0 +1,770 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Migration\Version;
|
||||
|
||||
use Appwrite\Migration\Migration;
|
||||
use Utopia\App;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
|
||||
class V14 extends Migration
|
||||
{
|
||||
/**
|
||||
* @var \PDO $pdo
|
||||
*/
|
||||
private $pdo;
|
||||
|
||||
public function execute(): void
|
||||
{
|
||||
global $register;
|
||||
$this->pdo = $register->get('db');
|
||||
|
||||
if ($this->project->getId() === 'console' && $this->project->getInternalId() !== 'console') {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable SubQueries for Speed.
|
||||
*/
|
||||
foreach (['subQueryAttributes', 'subQueryIndexes', 'subQueryPlatforms', 'subQueryDomains', 'subQueryKeys', 'subQueryWebhooks', 'subQuerySessions', 'subQueryTokens', 'subQueryMemberships'] as $name) {
|
||||
Database::addFilter($name, fn () => null, fn () => []);
|
||||
}
|
||||
|
||||
Console::log('Migrating project: ' . $this->project->getAttribute('name') . ' (' . $this->project->getId() . ')');
|
||||
Console::info('Migrating Collections');
|
||||
$this->migrateCollections();
|
||||
Console::info('Create Default Database Layer');
|
||||
$this->createDatabaseLayer();
|
||||
if ($this->project->getId() !== 'console') {
|
||||
Console::info('Migrating Database Collections');
|
||||
$this->migrateCustomCollections();
|
||||
}
|
||||
Console::info('Migrating Documents');
|
||||
$this->forEachDocument([$this, 'fixDocument']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the default Database for existing Projects.
|
||||
*
|
||||
* @return void
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function createDatabaseLayer(): void
|
||||
{
|
||||
try {
|
||||
if (!$this->projectDB->exists('databases')) {
|
||||
$this->createCollection('databases');
|
||||
}
|
||||
} catch (\Throwable $th) {
|
||||
Console::warning($th->getMessage());
|
||||
}
|
||||
|
||||
if ($this->project->getInternalId() === 'console') {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->projectDB->createDocument('databases', new Document([
|
||||
'$id' => 'default',
|
||||
'name' => 'Default',
|
||||
'search' => 'default Default'
|
||||
]));
|
||||
} catch (\Throwable $th) {
|
||||
Console::warning($th->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrates all Files.
|
||||
*
|
||||
* @param \Utopia\Database\Document $bucket
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function migrateBucketFiles(Document $bucket): void
|
||||
{
|
||||
$nextFile = null;
|
||||
do {
|
||||
$documents = $this->projectDB->find("bucket_{$bucket->getInternalId()}", limit: $this->limit, cursor: $nextFile);
|
||||
$count = count($documents);
|
||||
|
||||
foreach ($documents as $document) {
|
||||
go(function (Document $bucket, Document $document) {
|
||||
Console::log("Migrating File {$document->getId()}");
|
||||
try {
|
||||
/**
|
||||
* Migrate $createdAt.
|
||||
*/
|
||||
if (empty($document->getCreatedAt())) {
|
||||
$document->setAttribute('$createdAt', $document->getAttribute('dateCreated'));
|
||||
$this->projectDB->updateDocument("bucket_{$bucket->getInternalId()}", $document->getId(), $document);
|
||||
}
|
||||
} catch (\Throwable $th) {
|
||||
Console::warning($th->getMessage());
|
||||
}
|
||||
}, $bucket, $document);
|
||||
}
|
||||
|
||||
if ($count !== $this->limit) {
|
||||
$nextFile = null;
|
||||
} else {
|
||||
$nextFile = end($documents);
|
||||
}
|
||||
} while (!is_null($nextFile));
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrates all Database Collections.
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function migrateCustomCollections(): void
|
||||
{
|
||||
try {
|
||||
$this->pdo->prepare("ALTER TABLE IF EXISTS `{$this->projectDB->getDefaultDatabase()}`.`_{$this->project->getInternalId()}_collections` RENAME TO `_{$this->project->getInternalId()}_database_1`")->execute();
|
||||
} catch (\Throwable $th) {
|
||||
Console::warning($th->getMessage());
|
||||
}
|
||||
try {
|
||||
$this->pdo->prepare("ALTER TABLE IF EXISTS `{$this->projectDB->getDefaultDatabase()}`.`_{$this->project->getInternalId()}_collections_perms` RENAME TO `_{$this->project->getInternalId()}_database_1_perms`")->execute();
|
||||
} catch (\Throwable $th) {
|
||||
Console::warning($th->getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* Update metadata table.
|
||||
*/
|
||||
try {
|
||||
$this->pdo->prepare("UPDATE `{$this->projectDB->getDefaultDatabase()}`.`_{$this->project->getInternalId()}__metadata`
|
||||
SET
|
||||
_uid = 'database_1',
|
||||
name = 'database_1'
|
||||
WHERE _uid = 'collections';
|
||||
")->execute();
|
||||
} catch (\Throwable $th) {
|
||||
Console::warning($th->getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
/**
|
||||
* Add Database ID for Collections.
|
||||
*/
|
||||
$this->createAttributeFromCollection($this->projectDB, 'database_1', 'databaseId', 'collections');
|
||||
|
||||
/**
|
||||
* Add Database Internal ID for Collections.
|
||||
*/
|
||||
$this->createAttributeFromCollection($this->projectDB, 'database_1', 'databaseInternalId', 'collections');
|
||||
} catch (\Throwable $th) {
|
||||
Console::warning($th->getMessage());
|
||||
}
|
||||
|
||||
$nextCollection = null;
|
||||
|
||||
do {
|
||||
$documents = $this->projectDB->find('database_1', limit: $this->limit, cursor: $nextCollection);
|
||||
$count = count($documents);
|
||||
|
||||
\Co\run(function (array $documents) {
|
||||
foreach ($documents as $document) {
|
||||
go(function (Document $collection) {
|
||||
$id = $collection->getId();
|
||||
$internalId = $collection->getInternalId();
|
||||
|
||||
Console::log("- {$id} ({$collection->getAttribute('name')})");
|
||||
|
||||
try {
|
||||
/**
|
||||
* Rename user's colletion table schema
|
||||
*/
|
||||
$this->createNewMetaData("collection_{$internalId}", "database_1_collection_{$internalId}");
|
||||
} catch (\Throwable $th) {
|
||||
Console::warning($th->getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
/**
|
||||
* Update metadata table.
|
||||
*/
|
||||
$this->pdo->prepare("UPDATE `{$this->projectDB->getDefaultDatabase()}`.`_{$this->project->getInternalId()}__metadata`
|
||||
SET
|
||||
_uid = 'database_1_collection_{$internalId}',
|
||||
name = 'database_1_collection_{$internalId}'
|
||||
WHERE _uid = 'collection_{$internalId}';
|
||||
")->execute();
|
||||
} catch (\Throwable $th) {
|
||||
Console::warning($th->getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
/**
|
||||
* Update internal ID's.
|
||||
*/
|
||||
$collection
|
||||
->setAttribute('databaseId', 'default')
|
||||
->setAttribute('databaseInternalId', '1');
|
||||
$this->projectDB->updateDocument('database_1', $collection->getId(), $collection);
|
||||
} catch (\Throwable $th) {
|
||||
Console::warning($th->getMessage());
|
||||
}
|
||||
}, $document);
|
||||
}
|
||||
}, $documents);
|
||||
|
||||
if ($count !== $this->limit) {
|
||||
$nextCollection = null;
|
||||
} else {
|
||||
$nextCollection = end($documents);
|
||||
}
|
||||
} while (!is_null($nextCollection));
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate all Collections.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function migrateCollections(): void
|
||||
{
|
||||
foreach ($this->collections as $collection) {
|
||||
$id = $collection['$id'];
|
||||
|
||||
Console::log("- {$id}");
|
||||
|
||||
$this->createNewMetaData($id);
|
||||
|
||||
$this->projectDB->setNamespace("_{$this->project->getInternalId()}");
|
||||
|
||||
switch ($id) {
|
||||
case 'attributes':
|
||||
case 'indexes':
|
||||
try {
|
||||
/**
|
||||
* Create 'databaseInternalId' attribute
|
||||
*/
|
||||
$this->createAttributeFromCollection($this->projectDB, $id, 'databaseId');
|
||||
} catch (\Throwable $th) {
|
||||
Console::warning("'databaseInternalId' from {$id}: {$th->getMessage()}");
|
||||
}
|
||||
try {
|
||||
/**
|
||||
* Create 'databaseInternalId' attribute
|
||||
*/
|
||||
$this->createAttributeFromCollection($this->projectDB, $id, 'databaseInternalId');
|
||||
} catch (\Throwable $th) {
|
||||
Console::warning("'databaseInternalId' from {$id}: {$th->getMessage()}");
|
||||
}
|
||||
|
||||
try {
|
||||
/**
|
||||
* Create 'collectionInternalId' attribute
|
||||
*/
|
||||
$this->createAttributeFromCollection($this->projectDB, $id, 'collectionInternalId');
|
||||
} catch (\Throwable $th) {
|
||||
Console::warning("'collectionInternalId' from {$id}: {$th->getMessage()}");
|
||||
}
|
||||
|
||||
try {
|
||||
/**
|
||||
* Re-Create '_key_collection' index
|
||||
*/
|
||||
@$this->projectDB->deleteIndex($id, '_key_collection');
|
||||
$this->createIndexFromCollection($this->projectDB, $id, '_key_db_collection');
|
||||
} catch (\Throwable $th) {
|
||||
Console::warning("'_key_collection' from {$id}: {$th->getMessage()}");
|
||||
}
|
||||
|
||||
break;
|
||||
case 'projects':
|
||||
try {
|
||||
/**
|
||||
* Create 'teamInternalId' attribute
|
||||
*/
|
||||
$this->createAttributeFromCollection($this->projectDB, $id, 'teamInternalId');
|
||||
} catch (\Throwable $th) {
|
||||
Console::warning("'teamInternalId' from {$id}: {$th->getMessage()}");
|
||||
}
|
||||
|
||||
break;
|
||||
case 'platforms':
|
||||
case 'domains':
|
||||
try {
|
||||
/**
|
||||
* Create 'projectInternalId' attribute
|
||||
*/
|
||||
$this->createAttributeFromCollection($this->projectDB, $id, 'projectInternalId');
|
||||
} catch (\Throwable $th) {
|
||||
Console::warning("'projectInternalId' from {$id}: {$th->getMessage()}");
|
||||
}
|
||||
try {
|
||||
/**
|
||||
* Re-Create '_key_project' index
|
||||
*/
|
||||
@$this->projectDB->deleteIndex($id, '_key_project');
|
||||
$this->createIndexFromCollection($this->projectDB, $id, '_key_project');
|
||||
} catch (\Throwable $th) {
|
||||
Console::warning("'_key_project' from {$id}: {$th->getMessage()}");
|
||||
}
|
||||
|
||||
break;
|
||||
case 'keys':
|
||||
try {
|
||||
/**
|
||||
* Create 'projectInternalId' attribute
|
||||
*/
|
||||
$this->createAttributeFromCollection($this->projectDB, $id, 'projectInternalId');
|
||||
} catch (\Throwable $th) {
|
||||
Console::warning("'projectInternalId' from {$id}: {$th->getMessage()}");
|
||||
}
|
||||
try {
|
||||
/**
|
||||
* Create 'expire' attribute
|
||||
*/
|
||||
$this->createAttributeFromCollection($this->projectDB, $id, 'expire');
|
||||
} catch (\Throwable $th) {
|
||||
Console::warning("'expire' from {$id}: {$th->getMessage()}");
|
||||
}
|
||||
try {
|
||||
/**
|
||||
* Re-Create '_key_project' index
|
||||
*/
|
||||
@$this->projectDB->deleteIndex($id, '_key_project');
|
||||
$this->createIndexFromCollection($this->projectDB, $id, '_key_project');
|
||||
} catch (\Throwable $th) {
|
||||
Console::warning("'_key_project' from {$id}: {$th->getMessage()}");
|
||||
}
|
||||
|
||||
break;
|
||||
case 'webhooks':
|
||||
try {
|
||||
/**
|
||||
* Create 'signatureKey' attribute
|
||||
*/
|
||||
$this->createAttributeFromCollection($this->projectDB, $id, 'signatureKey');
|
||||
} catch (\Throwable $th) {
|
||||
Console::warning("'signatureKey' from {$id}: {$th->getMessage()}");
|
||||
}
|
||||
try {
|
||||
/**
|
||||
* Create 'projectInternalId' attribute
|
||||
*/
|
||||
$this->createAttributeFromCollection($this->projectDB, $id, 'projectInternalId');
|
||||
} catch (\Throwable $th) {
|
||||
Console::warning("'projectInternalId' from {$id}: {$th->getMessage()}");
|
||||
}
|
||||
try {
|
||||
/**
|
||||
* Re-Create '_key_project' index
|
||||
*/
|
||||
@$this->projectDB->deleteIndex($id, '_key_project');
|
||||
$this->createIndexFromCollection($this->projectDB, $id, '_key_project');
|
||||
} catch (\Throwable $th) {
|
||||
Console::warning("'_key_project' from {$id}: {$th->getMessage()}");
|
||||
}
|
||||
|
||||
break;
|
||||
case 'users':
|
||||
try {
|
||||
/**
|
||||
* Create 'phone' attribute
|
||||
*/
|
||||
$this->createAttributeFromCollection($this->projectDB, $id, 'phone');
|
||||
} catch (\Throwable $th) {
|
||||
Console::warning("'phone' from {$id}: {$th->getMessage()}");
|
||||
}
|
||||
try {
|
||||
/**
|
||||
* Create 'phoneVerification' attribute
|
||||
*/
|
||||
$this->createAttributeFromCollection($this->projectDB, $id, 'phoneVerification');
|
||||
} catch (\Throwable $th) {
|
||||
Console::warning("'phoneVerification' from {$id}: {$th->getMessage()}");
|
||||
}
|
||||
try {
|
||||
/**
|
||||
* Create '_key_phone' index
|
||||
*/
|
||||
$this->createIndexFromCollection($this->projectDB, $id, '_key_phone');
|
||||
} catch (\Throwable $th) {
|
||||
Console::warning("'_key_phone' from {$id}: {$th->getMessage()}");
|
||||
}
|
||||
|
||||
break;
|
||||
case 'tokens':
|
||||
case 'sessions':
|
||||
try {
|
||||
/**
|
||||
* Create 'userInternalId' attribute
|
||||
*/
|
||||
$this->createAttributeFromCollection($this->projectDB, $id, 'userInternalId');
|
||||
} catch (\Throwable $th) {
|
||||
Console::warning("'userInternalId' from {$id}: {$th->getMessage()}");
|
||||
}
|
||||
try {
|
||||
/**
|
||||
* Re-Create '_key_user' index
|
||||
*/
|
||||
@$this->projectDB->deleteIndex($id, '_key_user');
|
||||
$this->createIndexFromCollection($this->projectDB, $id, '_key_user');
|
||||
} catch (\Throwable $th) {
|
||||
Console::warning("'_key_user' from {$id}: {$th->getMessage()}");
|
||||
}
|
||||
|
||||
break;
|
||||
case 'memberships':
|
||||
try {
|
||||
/**
|
||||
* Create 'teamInternalId' attribute
|
||||
*/
|
||||
$this->createAttributeFromCollection($this->projectDB, $id, 'teamInternalId');
|
||||
} catch (\Throwable $th) {
|
||||
Console::warning("'teamInternalId' from {$id}: {$th->getMessage()}");
|
||||
}
|
||||
try {
|
||||
/**
|
||||
* Create 'userInternalId' attribute
|
||||
*/
|
||||
$this->createAttributeFromCollection($this->projectDB, $id, 'userInternalId');
|
||||
} catch (\Throwable $th) {
|
||||
Console::warning("'userInternalId' from {$id}: {$th->getMessage()}");
|
||||
}
|
||||
try {
|
||||
/**
|
||||
* Re-Create '_key_unique' index
|
||||
*/
|
||||
@$this->projectDB->deleteIndex($id, '_key_unique');
|
||||
$this->createIndexFromCollection($this->projectDB, $id, '_key_unique');
|
||||
} catch (\Throwable $th) {
|
||||
Console::warning("'_key_unique' from {$id}: {$th->getMessage()}");
|
||||
}
|
||||
try {
|
||||
/**
|
||||
* Re-Create '_key_team' index
|
||||
*/
|
||||
@$this->projectDB->deleteIndex($id, '_key_team');
|
||||
$this->createIndexFromCollection($this->projectDB, $id, '_key_team');
|
||||
} catch (\Throwable $th) {
|
||||
Console::warning("'_key_team' from {$id}: {$th->getMessage()}");
|
||||
}
|
||||
try {
|
||||
/**
|
||||
* Re-Create '_key_user' index
|
||||
*/
|
||||
@$this->projectDB->deleteIndex($id, '_key_user');
|
||||
$this->createIndexFromCollection($this->projectDB, $id, '_key_user');
|
||||
} catch (\Throwable $th) {
|
||||
Console::warning("'_key_user' from {$id}: {$th->getMessage()}");
|
||||
}
|
||||
break;
|
||||
}
|
||||
usleep(50000);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix run on each document
|
||||
*
|
||||
* @param \Utopia\Database\Document $document
|
||||
* @return \Utopia\Database\Document
|
||||
*/
|
||||
protected function fixDocument(Document $document)
|
||||
{
|
||||
switch ($document->getCollection()) {
|
||||
case 'projects':
|
||||
/**
|
||||
* Bump Project version number.
|
||||
*/
|
||||
$document->setAttribute('version', '0.15.0');
|
||||
|
||||
if (!empty($document->getAttribute('teamId')) && is_null($document->getAttribute('teamInternalId'))) {
|
||||
$internalId = $this->projectDB->getDocument('teams', $document->getAttribute('teamId'))->getInternalId();
|
||||
$document->setAttribute('teamInternalId', $internalId);
|
||||
}
|
||||
|
||||
break;
|
||||
case 'keys':
|
||||
/**
|
||||
* Add new 'expire' attribute and default to never (0).
|
||||
*/
|
||||
if (is_null($document->getAttribute('expire'))) {
|
||||
$document->setAttribute('expire', 0);
|
||||
}
|
||||
/**
|
||||
* Add Internal ID 'projectId' for Subqueries.
|
||||
*/
|
||||
if (!empty($document->getAttribute('projectId')) && is_null($document->getAttribute('projectInternalId'))) {
|
||||
$internalId = $this->projectDB->getDocument('projects', $document->getAttribute('projectId'))->getInternalId();
|
||||
$document->setAttribute('projectInternalId', $internalId);
|
||||
}
|
||||
|
||||
break;
|
||||
case 'audit':
|
||||
/**
|
||||
* Add Database Layer to collection resource.
|
||||
*/
|
||||
if (str_starts_with($document->getAttribute('resource'), 'collection/')) {
|
||||
$document
|
||||
->setAttribute('resource', "database/default/{$document->getAttribute('resource')}")
|
||||
->setAttribute('event', "databases.default.{$document->getAttribute('event')}");
|
||||
}
|
||||
|
||||
if (str_starts_with($document->getAttribute('resource'), 'document/')) {
|
||||
$collectionId = explode('.', $document->getAttribute('event'))[1];
|
||||
$document
|
||||
->setAttribute('resource', "database/default/collection/{$collectionId}/{$document->getAttribute('resource')}")
|
||||
->setAttribute('event', "databases.default.{$document->getAttribute('event')}");
|
||||
}
|
||||
|
||||
break;
|
||||
case 'stats':
|
||||
/**
|
||||
* Add Database Layer to stats metric.
|
||||
*/
|
||||
if (str_starts_with($document->getAttribute('metric'), 'database.')) {
|
||||
$metric = ltrim($document->getAttribute('metric'), 'database.');
|
||||
$document->setAttribute('metric', "databases.default.{$metric}");
|
||||
}
|
||||
|
||||
break;
|
||||
case 'webhooks':
|
||||
/**
|
||||
* Add new 'signatureKey' attribute and generate a random value.
|
||||
*/
|
||||
if (empty($document->getAttribute('signatureKey'))) {
|
||||
$document->setAttribute('signatureKey', \bin2hex(\random_bytes(64)));
|
||||
}
|
||||
/**
|
||||
* Add Internal ID 'projectId' for Subqueries.
|
||||
*/
|
||||
if (!empty($document->getAttribute('projectId')) && is_null($document->getAttribute('projectInternalId'))) {
|
||||
$internalId = $this->projectDB->getDocument('projects', $document->getAttribute('projectId'))->getInternalId();
|
||||
$document->setAttribute('projectInternalId', $internalId);
|
||||
}
|
||||
|
||||
break;
|
||||
case 'domains':
|
||||
/**
|
||||
* Add Internal ID 'projectId' for Subqueries.
|
||||
*/
|
||||
if (!empty($document->getAttribute('projectId')) && is_null($document->getAttribute('projectInternalId'))) {
|
||||
$internalId = $this->projectDB->getDocument('projects', $document->getAttribute('projectId'))->getInternalId();
|
||||
$document->setAttribute('projectInternalId', $internalId);
|
||||
}
|
||||
|
||||
break;
|
||||
case 'tokens':
|
||||
case 'sessions':
|
||||
/**
|
||||
* Add Internal ID 'userId' for Subqueries.
|
||||
*/
|
||||
if (!empty($document->getAttribute('userId')) && is_null($document->getAttribute('userInternalId'))) {
|
||||
$internalId = $this->projectDB->getDocument('users', $document->getAttribute('userId'))->getInternalId();
|
||||
$document->setAttribute('userInternalId', $internalId);
|
||||
}
|
||||
|
||||
break;
|
||||
case 'memberships':
|
||||
/**
|
||||
* Add Internal ID 'userId' for Subqueries.
|
||||
*/
|
||||
if (!empty($document->getAttribute('userId')) && is_null($document->getAttribute('userInternalId'))) {
|
||||
$internalId = $this->projectDB->getDocument('users', $document->getAttribute('userId'))->getInternalId();
|
||||
$document->setAttribute('userInternalId', $internalId);
|
||||
}
|
||||
/**
|
||||
* Add Internal ID 'teamId' for Subqueries.
|
||||
*/
|
||||
if (!empty($document->getAttribute('teamId')) && is_null($document->getAttribute('teamInternalId'))) {
|
||||
$internalId = $this->projectDB->getDocument('teams', $document->getAttribute('teamId'))->getInternalId();
|
||||
$document->setAttribute('teamInternalId', $internalId);
|
||||
}
|
||||
|
||||
break;
|
||||
case 'attributes':
|
||||
case 'indexes':
|
||||
/**
|
||||
* Add Internal ID 'collectionId' for Subqueries.
|
||||
*/
|
||||
if (!empty($document->getAttribute('collectionId')) && is_null($document->getAttribute('collectionInternalId'))) {
|
||||
$internalId = $this->projectDB->getDocument('database_1', $document->getAttribute('collectionId'))->getInternalId();
|
||||
$document->setAttribute('collectionInternalId', $internalId);
|
||||
}
|
||||
/**
|
||||
* Add Internal ID 'databaseInternalId' for Subqueries.
|
||||
*/
|
||||
if (is_null($document->getAttribute('databaseInternalId'))) {
|
||||
$document->setAttribute('databaseInternalId', '1');
|
||||
}
|
||||
/**
|
||||
* Add Internal ID 'databaseInternalId' for Subqueries.
|
||||
*/
|
||||
if (is_null($document->getAttribute('databaseId'))) {
|
||||
$document->setAttribute('databaseId', 'default');
|
||||
}
|
||||
|
||||
try {
|
||||
/**
|
||||
* Re-create Collection Document
|
||||
*/
|
||||
$this->projectDB->deleteDocument($document->getCollection(), $document->getId());
|
||||
$this->projectDB->createDocument($document->getCollection(), $document->setAttribute('$id', "1_{$document->getInternalId()}_{$document->getAttribute('key')}"));
|
||||
} catch (\Throwable $th) {
|
||||
Console::warning("Create Collection Document - {$th->getMessage()}");
|
||||
}
|
||||
$document = null;
|
||||
break;
|
||||
case 'platforms':
|
||||
/**
|
||||
* Migrate dateCreated to $createdAt.
|
||||
*/
|
||||
if (empty($document->getCreatedAt())) {
|
||||
$document->setAttribute('$createdAt', $document->getAttribute('dateCreated'));
|
||||
}
|
||||
/**
|
||||
* Migrate dateUpdated to $updatedAt.
|
||||
*/
|
||||
if (empty($document->getUpdatedAt())) {
|
||||
$document->setAttribute('$updatedAt', $document->getAttribute('dateUpdated'));
|
||||
}
|
||||
/**
|
||||
* Add Internal ID 'projectId' for Subqueries.
|
||||
*/
|
||||
if (!empty($document->getAttribute('projectId')) && is_null($document->getAttribute('projectInternalId'))) {
|
||||
$internalId = $this->projectDB->getDocument('projects', $document->getAttribute('projectId'))->getInternalId();
|
||||
$document->setAttribute('projectInternalId', $internalId);
|
||||
}
|
||||
|
||||
break;
|
||||
case 'buckets':
|
||||
/**
|
||||
* Migrate dateCreated to $createdAt.
|
||||
*/
|
||||
if (empty($document->getCreatedAt())) {
|
||||
$document->setAttribute('$createdAt', $document->getAttribute('dateCreated'));
|
||||
}
|
||||
/**
|
||||
* Migrate dateUpdated to $updatedAt.
|
||||
*/
|
||||
if (empty($document->getUpdatedAt())) {
|
||||
$document->setAttribute('$updatedAt', $document->getAttribute('dateUpdated'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate all Storage Buckets to use Internal ID.
|
||||
*/
|
||||
$internalId = $this->projectDB->getDocument('buckets', $document->getId())->getInternalId();
|
||||
$this->createNewMetaData("bucket_{$internalId}");
|
||||
|
||||
/**
|
||||
* Migrate all Storage Bucket Files.
|
||||
*/
|
||||
$this->migrateBucketFiles($document);
|
||||
|
||||
break;
|
||||
case 'users':
|
||||
/**
|
||||
* Set 'phoneVerification' to false if not set.
|
||||
*/
|
||||
if (is_null($document->getAttribute('phoneVerification'))) {
|
||||
$document->setAttribute('phoneVerification', false);
|
||||
}
|
||||
|
||||
break;
|
||||
case 'functions':
|
||||
/**
|
||||
* Migrate dateCreated to $createdAt.
|
||||
*/
|
||||
if (empty($document->getCreatedAt())) {
|
||||
$document->setAttribute('$createdAt', $document->getAttribute('dateCreated'));
|
||||
}
|
||||
/**
|
||||
* Migrate dateUpdated to $updatedAt.
|
||||
*/
|
||||
if (empty($document->getUpdatedAt())) {
|
||||
$document->setAttribute('$updatedAt', $document->getAttribute('dateUpdated'));
|
||||
}
|
||||
|
||||
break;
|
||||
case 'deployments':
|
||||
case 'executions':
|
||||
case 'teams':
|
||||
/**
|
||||
* Migrate dateCreated to $createdAt.
|
||||
*/
|
||||
if (empty($document->getCreatedAt())) {
|
||||
$document->setAttribute('$createdAt', $document->getAttribute('dateCreated'));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return $document;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new metadata that was introduced for a collection and enforces the Internal ID.
|
||||
*
|
||||
* @param string $id
|
||||
* @return void
|
||||
*/
|
||||
protected function createNewMetaData(string $id, string $to = null): void
|
||||
{
|
||||
$to ??= $id;
|
||||
/**
|
||||
* Skip files collection.
|
||||
*/
|
||||
if (in_array($id, ['files', 'databases'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
/**
|
||||
* Replace project UID with Internal ID.
|
||||
*/
|
||||
$this->pdo->prepare("ALTER TABLE IF EXISTS `{$this->projectDB->getDefaultDatabase()}`.`_{$this->project->getId()}_{$id}` RENAME TO `_{$this->project->getInternalId()}_{$to}`")->execute();
|
||||
} catch (\Throwable $th) {
|
||||
Console::warning("Migrating {$id} Collection: {$th->getMessage()}");
|
||||
}
|
||||
try {
|
||||
/**
|
||||
* Replace project UID with Internal ID on permissions table.
|
||||
*/
|
||||
$this->pdo->prepare("ALTER TABLE IF EXISTS `{$this->projectDB->getDefaultDatabase()}`.`_{$this->project->getId()}_{$id}_perms` RENAME TO `_{$this->project->getInternalId()}_{$to}_perms`")->execute();
|
||||
} catch (\Throwable $th) {
|
||||
Console::warning("Migrating {$id} Collection: {$th->getMessage()}");
|
||||
}
|
||||
try {
|
||||
/**
|
||||
* Add _createdAt attribute.
|
||||
*/
|
||||
$this->pdo->prepare("ALTER TABLE `_{$this->project->getInternalId()}_{$to}` ADD COLUMN IF NOT EXISTS `_createdAt` int unsigned DEFAULT NULL")->execute();
|
||||
} catch (\Throwable $th) {
|
||||
Console::warning("Migrating {$id} Collection: {$th->getMessage()}");
|
||||
}
|
||||
try {
|
||||
/**
|
||||
* Add _updatedAt attribute.
|
||||
*/
|
||||
$this->pdo->prepare("ALTER TABLE `_{$this->project->getInternalId()}_{$to}` ADD COLUMN IF NOT EXISTS `_updatedAt` int unsigned DEFAULT NULL")->execute();
|
||||
} catch (\Throwable $th) {
|
||||
Console::warning("Migrating {$id} Collection: {$th->getMessage()}");
|
||||
}
|
||||
try {
|
||||
/**
|
||||
* Create index for _createdAt.
|
||||
*/
|
||||
$this->pdo->prepare("CREATE INDEX IF NOT EXISTS `_created_at` ON `_{$this->project->getInternalId()}_{$to}` (`_createdAt`)")->execute();
|
||||
} catch (\Throwable $th) {
|
||||
Console::warning("Migrating {$id} Collection: {$th->getMessage()}");
|
||||
}
|
||||
try {
|
||||
/**
|
||||
* Create index for _updatedAt.
|
||||
*/
|
||||
$this->pdo->prepare("CREATE INDEX IF NOT EXISTS `_updated_at` ON `_{$this->project->getInternalId()}_{$to}` (`_updatedAt`)")->execute();
|
||||
} catch (\Throwable $th) {
|
||||
Console::warning("Migrating {$id} Collection: {$th->getMessage()}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ use Utopia\Storage\Device\Wasabi;
|
||||
use Utopia\Storage\Device\Backblaze;
|
||||
use Utopia\Storage\Device\S3;
|
||||
use Exception;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
|
||||
abstract class Worker
|
||||
{
|
||||
@@ -82,8 +83,8 @@ abstract class Worker
|
||||
throw new Exception("Please implement shutdown method in worker");
|
||||
}
|
||||
|
||||
const DATABASE_PROJECT = 'project';
|
||||
const DATABASE_CONSOLE = 'console';
|
||||
public const DATABASE_PROJECT = 'project';
|
||||
public const DATABASE_CONSOLE = 'console';
|
||||
|
||||
/**
|
||||
* A wrapper around 'init' function with non-worker-specific code
|
||||
@@ -113,6 +114,11 @@ abstract class Worker
|
||||
public function perform(): void
|
||||
{
|
||||
try {
|
||||
/**
|
||||
* Disabling global authorization in workers.
|
||||
*/
|
||||
Authorization::disable();
|
||||
Authorization::setDefaultStatus(false);
|
||||
$this->run();
|
||||
} catch (\Throwable $error) {
|
||||
foreach (self::$errorCallbacks as $errorCallback) {
|
||||
@@ -160,7 +166,16 @@ abstract class Worker
|
||||
*/
|
||||
protected function getProjectDB(string $projectId): Database
|
||||
{
|
||||
return $this->getDB(self::DATABASE_PROJECT, $projectId);
|
||||
$consoleDB = $this->getConsoleDB();
|
||||
|
||||
if ($projectId === 'console') {
|
||||
return $consoleDB;
|
||||
}
|
||||
|
||||
/** @var Document $project */
|
||||
$project = Authorization::skip(fn() => $consoleDB->getDocument('projects', $projectId));
|
||||
|
||||
return $this->getDB(self::DATABASE_PROJECT, $projectId, $project->getInternalId());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -178,7 +193,7 @@ abstract class Worker
|
||||
* @param string $projectId of internal or external DB
|
||||
* @return Database
|
||||
*/
|
||||
private function getDB($type, $projectId = ''): Database
|
||||
private function getDB(string $type, string $projectId = '', string $projectInternalId = ''): Database
|
||||
{
|
||||
global $register;
|
||||
|
||||
@@ -190,7 +205,7 @@ abstract class Worker
|
||||
if (!$projectId) {
|
||||
throw new \Exception('ProjectID not provided - cannot get database');
|
||||
}
|
||||
$namespace = "_{$projectId}";
|
||||
$namespace = "_{$projectInternalId}";
|
||||
break;
|
||||
case self::DATABASE_CONSOLE:
|
||||
$namespace = "_console";
|
||||
|
||||
@@ -263,6 +263,12 @@ class OpenAPI3 extends Format
|
||||
'required' => !$param['optional'],
|
||||
];
|
||||
|
||||
foreach ($this->services as $service) {
|
||||
if ($route->getLabel('sdk.namespace', 'default') === $service['name'] && in_array($name, $service['x-globalAttributes'] ?? [])) {
|
||||
$node['x-global'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
switch ((!empty($validator)) ? \get_class($validator) : '') {
|
||||
case 'Utopia\Validator\Text':
|
||||
$node['schema']['type'] = $validator->getType();
|
||||
@@ -390,6 +396,10 @@ class OpenAPI3 extends Format
|
||||
if (\array_key_exists('items', $node['schema'])) {
|
||||
$body['content'][$consumes[0]]['schema']['properties'][$name]['items'] = $node['schema']['items'];
|
||||
}
|
||||
|
||||
if ($node['x-global'] ?? false) {
|
||||
$body['content'][$consumes[0]]['schema']['properties'][$name]['x-global'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
$url = \str_replace(':' . $name, '{' . $name . '}', $url);
|
||||
|
||||
@@ -252,6 +252,12 @@ class Swagger2 extends Format
|
||||
'required' => !$param['optional'],
|
||||
];
|
||||
|
||||
foreach ($this->services as $service) {
|
||||
if ($route->getLabel('sdk.namespace', 'default') === $service['name'] && in_array($name, $service['x-globalAttributes'] ?? [])) {
|
||||
$node['x-global'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
switch ((!empty($validator)) ? \get_class($validator) : '') {
|
||||
case 'Utopia\Validator\Text':
|
||||
$node['type'] = $validator->getType();
|
||||
@@ -378,6 +384,10 @@ class Swagger2 extends Format
|
||||
'x-example' => $node['x-example'] ?? null,
|
||||
];
|
||||
|
||||
if ($node['x-global'] ?? false) {
|
||||
$body['schema']['properties'][$name]['x-global'] = true;
|
||||
}
|
||||
|
||||
if (\array_key_exists('items', $node)) {
|
||||
$body['schema']['properties'][$name]['items'] = $node['items'];
|
||||
}
|
||||
|
||||
@@ -113,20 +113,24 @@ class Stats
|
||||
$this->statsd->count('network.all' . $tags, $networkRequestSize + $networkResponseSize);
|
||||
|
||||
$dbMetrics = [
|
||||
'database.collections.create',
|
||||
'database.collections.read',
|
||||
'database.collections.update',
|
||||
'database.collections.delete',
|
||||
'database.documents.create',
|
||||
'database.documents.read',
|
||||
'database.documents.update',
|
||||
'database.documents.delete',
|
||||
'databases.create',
|
||||
'databases.read',
|
||||
'databases.update',
|
||||
'databases.delete',
|
||||
'databases.collections.create',
|
||||
'databases.collections.read',
|
||||
'databases.collections.update',
|
||||
'databases.collections.delete',
|
||||
'databases.documents.create',
|
||||
'databases.documents.read',
|
||||
'databases.documents.update',
|
||||
'databases.documents.delete',
|
||||
];
|
||||
|
||||
foreach ($dbMetrics as $metric) {
|
||||
$value = $this->params[$metric] ?? 0;
|
||||
if ($value >= 1) {
|
||||
$tags = ",projectId={$projectId},collectionId=" . ($this->params['collectionId'] ?? '');
|
||||
$tags = ",projectId={$projectId},collectionId=" . ($this->params['collectionId'] ?? '') . ",databaseId=" . ($this->params['databaseId'] ?? '');
|
||||
$this->statsd->increment($metric . $tags);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,351 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Stats;
|
||||
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use InfluxDB\Database as InfluxDatabase;
|
||||
use DateTime;
|
||||
|
||||
class Usage
|
||||
{
|
||||
protected InfluxDatabase $influxDB;
|
||||
protected Database $database;
|
||||
protected $errorHandler;
|
||||
private array $latestTime = [];
|
||||
|
||||
// all the mertics that we are collecting
|
||||
protected array $metrics = [
|
||||
'requests' => [
|
||||
'table' => 'appwrite_usage_requests_all',
|
||||
],
|
||||
'network' => [
|
||||
'table' => 'appwrite_usage_network_all',
|
||||
],
|
||||
'executions' => [
|
||||
'table' => 'appwrite_usage_executions_all',
|
||||
],
|
||||
'databases.create' => [
|
||||
'table' => 'appwrite_usage_databases_create',
|
||||
],
|
||||
'databases.read' => [
|
||||
'table' => 'appwrite_usage_databases_read',
|
||||
],
|
||||
'databases.update' => [
|
||||
'table' => 'appwrite_usage_databases_update',
|
||||
],
|
||||
'databases.delete' => [
|
||||
'table' => 'appwrite_usage_databases_delete',
|
||||
],
|
||||
'databases.collections.create' => [
|
||||
'table' => 'appwrite_usage_databases_collections_create',
|
||||
],
|
||||
'databases.collections.read' => [
|
||||
'table' => 'appwrite_usage_databases_collections_read',
|
||||
],
|
||||
'databases.collections.update' => [
|
||||
'table' => 'appwrite_usage_databases_collections_update',
|
||||
],
|
||||
'databases.collections.delete' => [
|
||||
'table' => 'appwrite_usage_databases_collections_delete',
|
||||
],
|
||||
'databases.documents.create' => [
|
||||
'table' => 'appwrite_usage_databases_documents_create',
|
||||
],
|
||||
'databases.documents.read' => [
|
||||
'table' => 'appwrite_usage_databases_documents_read',
|
||||
],
|
||||
'databases.documents.update' => [
|
||||
'table' => 'appwrite_usage_databases_documents_update',
|
||||
],
|
||||
'databases.documents.delete' => [
|
||||
'table' => 'appwrite_usage_databases_documents_delete',
|
||||
],
|
||||
'databases.databaseId.collections.create' => [
|
||||
'table' => 'appwrite_usage_databases_collections_create',
|
||||
'groupBy' => ['databaseId'],
|
||||
],
|
||||
'databases.databaseId.collections.read' => [
|
||||
'table' => 'appwrite_usage_databases_collections_read',
|
||||
'groupBy' => ['databaseId'],
|
||||
],
|
||||
'databases.databaseId.collections.update' => [
|
||||
'table' => 'appwrite_usage_databases_collections_update',
|
||||
'groupBy' => ['databaseId'],
|
||||
],
|
||||
'databases.databaseId.collections.delete' => [
|
||||
'table' => 'appwrite_usage_databases_collections_delete',
|
||||
'groupBy' => ['databaseId'],
|
||||
],
|
||||
'databases.databaseId.documents.create' => [
|
||||
'table' => 'appwrite_usage_databases_documents_create',
|
||||
'groupBy' => ['databaseId'],
|
||||
],
|
||||
'databases.databaseId.documents.read' => [
|
||||
'table' => 'appwrite_usage_databases_documents_read',
|
||||
'groupBy' => ['databaseId'],
|
||||
],
|
||||
'database.databaseId.documents.update' => [
|
||||
'table' => 'appwrite_usage_databases_documents_update',
|
||||
'groupBy' => ['databaseId'],
|
||||
],
|
||||
'databases.databaseId.documents.delete' => [
|
||||
'table' => 'appwrite_usage_databases_documents_delete',
|
||||
'groupBy' => ['databaseId'],
|
||||
],
|
||||
'databases.databaseId.collections.collectionId.documents.create' => [
|
||||
'table' => 'appwrite_usage_databases_documents_create',
|
||||
'groupBy' => ['collectionId'],
|
||||
],
|
||||
'databases.databaseId.collections.collectionId.documents.read' => [
|
||||
'table' => 'appwrite_usage_databases_documents_read',
|
||||
'groupBy' => ['databaseId', 'collectionId'],
|
||||
],
|
||||
'databases.databaseId.collections.collectionId.documents.update' => [
|
||||
'table' => 'appwrite_usage_databases_documents_update',
|
||||
'groupBy' => ['databaseId', 'collectionId'],
|
||||
],
|
||||
'databases.databaseId.collections.collectionId.documents.delete' => [
|
||||
'table' => 'appwrite_usage_databases_documents_delete',
|
||||
'groupBy' => ['databaseId', 'collectionId'],
|
||||
],
|
||||
'storage.buckets.create' => [
|
||||
'table' => 'appwrite_usage_storage_buckets_create',
|
||||
],
|
||||
'storage.buckets.read' => [
|
||||
'table' => 'appwrite_usage_storage_buckets_read',
|
||||
],
|
||||
'storage.buckets.update' => [
|
||||
'table' => 'appwrite_usage_storage_buckets_update',
|
||||
],
|
||||
'storage.buckets.delete' => [
|
||||
'table' => 'appwrite_usage_storage_buckets_delete',
|
||||
],
|
||||
'storage.files.create' => [
|
||||
'table' => 'appwrite_usage_storage_files_create',
|
||||
],
|
||||
'storage.files.read' => [
|
||||
'table' => 'appwrite_usage_storage_files_read',
|
||||
],
|
||||
'storage.files.update' => [
|
||||
'table' => 'appwrite_usage_storage_files_update',
|
||||
],
|
||||
'storage.files.delete' => [
|
||||
'table' => 'appwrite_usage_storage_files_delete',
|
||||
],
|
||||
'storage.buckets.bucketId.files.create' => [
|
||||
'table' => 'appwrite_usage_storage_files_create',
|
||||
'groupBy' => ['bucketId'],
|
||||
],
|
||||
'storage.buckets.bucketId.files.read' => [
|
||||
'table' => 'appwrite_usage_storage_files_read',
|
||||
'groupBy' => ['bucketId'],
|
||||
],
|
||||
'storage.buckets.bucketId.files.update' => [
|
||||
'table' => 'appwrite_usage_storage_files_update',
|
||||
'groupBy' => ['bucketId'],
|
||||
],
|
||||
'storage.buckets.bucketId.files.delete' => [
|
||||
'table' => 'appwrite_usage_storage_files_delete',
|
||||
'groupBy' => ['bucketId'],
|
||||
],
|
||||
'users.create' => [
|
||||
'table' => 'appwrite_usage_users_create',
|
||||
],
|
||||
'users.read' => [
|
||||
'table' => 'appwrite_usage_users_read',
|
||||
],
|
||||
'users.update' => [
|
||||
'table' => 'appwrite_usage_users_update',
|
||||
],
|
||||
'users.delete' => [
|
||||
'table' => 'appwrite_usage_users_delete',
|
||||
],
|
||||
'users.sessions.create' => [
|
||||
'table' => 'appwrite_usage_users_sessions_create',
|
||||
],
|
||||
'users.sessions.provider.create' => [
|
||||
'table' => 'appwrite_usage_users_sessions_create',
|
||||
'groupBy' => ['provider'],
|
||||
],
|
||||
'users.sessions.delete' => [
|
||||
'table' => 'appwrite_usage_users_sessions_delete',
|
||||
],
|
||||
'functions.functionId.executions' => [
|
||||
'table' => 'appwrite_usage_executions_all',
|
||||
'groupBy' => ['functionId'],
|
||||
],
|
||||
'functions.functionId.compute' => [
|
||||
'table' => 'appwrite_usage_executions_time',
|
||||
'groupBy' => ['functionId'],
|
||||
],
|
||||
'functions.functionId.failures' => [
|
||||
'table' => 'appwrite_usage_executions_all',
|
||||
'groupBy' => ['functionId'],
|
||||
'filters' => [
|
||||
'functionStatus' => 'failed',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
protected array $periods = [
|
||||
[
|
||||
'key' => '30m',
|
||||
'multiplier' => 1800,
|
||||
'startTime' => '-24 hours',
|
||||
],
|
||||
[
|
||||
'key' => '1d',
|
||||
'multiplier' => 86400,
|
||||
'startTime' => '-90 days',
|
||||
],
|
||||
];
|
||||
|
||||
public function __construct(Database $database, InfluxDatabase $influxDB, callable $errorHandler = null)
|
||||
{
|
||||
$this->database = $database;
|
||||
$this->influxDB = $influxDB;
|
||||
$this->errorHandler = $errorHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create or Update Mertic
|
||||
* Create or update each metric in the stats collection for the given project
|
||||
*
|
||||
* @param string $projectId
|
||||
* @param int $time
|
||||
* @param string $period
|
||||
* @param string $metric
|
||||
* @param int $value
|
||||
* @param int $type
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function createOrUpdateMetric(string $projectId, int $time, string $period, string $metric, int $value, int $type): void
|
||||
{
|
||||
$id = \md5("{$time}_{$period}_{$metric}");
|
||||
$this->database->setNamespace('_console');
|
||||
$project = $this->database->getDocument('projects', $projectId);
|
||||
$this->database->setNamespace('_' . $project->getInternalId());
|
||||
|
||||
try {
|
||||
$document = $this->database->getDocument('stats', $id);
|
||||
if ($document->isEmpty()) {
|
||||
$this->database->createDocument('stats', new Document([
|
||||
'$id' => $id,
|
||||
'period' => $period,
|
||||
'time' => $time,
|
||||
'metric' => $metric,
|
||||
'value' => $value,
|
||||
'type' => $type,
|
||||
]));
|
||||
} else {
|
||||
$this->database->updateDocument(
|
||||
'stats',
|
||||
$document->getId(),
|
||||
$document->setAttribute('value', $value)
|
||||
);
|
||||
}
|
||||
$this->latestTime[$metric][$period] = $time;
|
||||
} 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 {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync From InfluxDB
|
||||
* Sync stats from influxDB to stats collection in the Appwrite database
|
||||
*
|
||||
* @param string $metric
|
||||
* @param array $options
|
||||
* @param array $period
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function syncFromInfluxDB(string $metric, array $options, array $period): void
|
||||
{
|
||||
$start = DateTime::createFromFormat('U', \strtotime($period['startTime']))->format(DateTime::RFC3339);
|
||||
if (!empty($this->latestTime[$metric][$period['key']])) {
|
||||
$start = DateTime::createFromFormat('U', $this->latestTime[$metric][$period['key']])->format(DateTime::RFC3339);
|
||||
}
|
||||
$end = DateTime::createFromFormat('U', \strtotime('now'))->format(DateTime::RFC3339);
|
||||
|
||||
$table = $options['table']; //Which influxdb table to query for this metric
|
||||
$groupBy = empty($options['groupBy']) ? '' : ', ' . implode(', ', array_map(fn($groupBy) => '"' . $groupBy . '" ', $options['groupBy'])); //Some sub level metrics may be grouped by other tags like collectionId, bucketId, etc
|
||||
|
||||
$filters = $options['filters'] ?? []; // Some metrics might have additional filters, like function's status
|
||||
if (!empty($filters)) {
|
||||
$filters = ' AND ' . implode(' AND ', array_map(fn ($filter, $value) => "\"{$filter}\"='{$value}'", array_keys($filters), array_values($filters)));
|
||||
} else {
|
||||
$filters = '';
|
||||
}
|
||||
|
||||
$query = "SELECT sum(value) AS \"value\" ";
|
||||
$query .= "FROM \"{$table}\" ";
|
||||
$query .= "WHERE \"time\" > '{$start}' ";
|
||||
$query .= "AND \"time\" < '{$end}' ";
|
||||
$query .= "AND \"metric_type\"='counter' {$filters} ";
|
||||
$query .= "GROUP BY time({$period['key']}), \"projectId\" {$groupBy} ";
|
||||
$query .= "FILL(null)";
|
||||
|
||||
$result = $this->influxDB->query($query);
|
||||
|
||||
$points = $result->getPoints();
|
||||
foreach ($points as $point) {
|
||||
$projectId = $point['projectId'];
|
||||
|
||||
if (!empty($projectId) && $projectId !== 'console') {
|
||||
$metricUpdated = $metric;
|
||||
|
||||
if (!empty($groupBy)) {
|
||||
foreach ($options['groupBy'] as $groupBy) {
|
||||
$groupedBy = $point[$groupBy] ?? '';
|
||||
if (empty($groupedBy)) {
|
||||
continue;
|
||||
}
|
||||
$metricUpdated = str_replace($groupBy, $groupedBy, $metric);
|
||||
}
|
||||
}
|
||||
|
||||
$time = \strtotime($point['time']);
|
||||
$value = (!empty($point['value'])) ? $point['value'] : 0;
|
||||
|
||||
$this->createOrUpdateMetric(
|
||||
$projectId,
|
||||
$time,
|
||||
$period['key'],
|
||||
$metricUpdated,
|
||||
$value,
|
||||
0
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect Stats
|
||||
* Collect all the stats from Influd DB to Database
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function collect(): void
|
||||
{
|
||||
foreach ($this->metrics as $metric => $options) { //for each metrics
|
||||
foreach ($this->periods as $period) { // aggregate data for each period
|
||||
try {
|
||||
$this->syncFromInfluxDB($metric, $options, $period);
|
||||
} catch (\Exception $e) {
|
||||
if (is_callable($this->errorHandler)) {
|
||||
call_user_func($this->errorHandler, $e);
|
||||
} else {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,277 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Stats;
|
||||
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
|
||||
class UsageDB extends Usage
|
||||
{
|
||||
public function __construct(Database $database, callable $errorHandler = null)
|
||||
{
|
||||
$this->database = $database;
|
||||
$this->errorHandler = $errorHandler;
|
||||
}
|
||||
/**
|
||||
* Create or Update Mertic
|
||||
* Create or update each metric in the stats collection for the given project
|
||||
*
|
||||
* @param string $projectId
|
||||
* @param string $metric
|
||||
* @param int $value
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
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']);
|
||||
$id = \md5("{$time}_{$period}_{$metric}");
|
||||
$this->database->setNamespace('_' . $projectId);
|
||||
|
||||
try {
|
||||
$document = $this->database->getDocument('stats', $id);
|
||||
if ($document->isEmpty()) {
|
||||
$this->database->createDocument('stats', new Document([
|
||||
'$id' => $id,
|
||||
'period' => $period,
|
||||
'time' => $time,
|
||||
'metric' => $metric,
|
||||
'value' => $value,
|
||||
'type' => 1,
|
||||
]));
|
||||
} else {
|
||||
$this->database->updateDocument(
|
||||
'stats',
|
||||
$document->getId(),
|
||||
$document->setAttribute('value', $value)
|
||||
);
|
||||
}
|
||||
} 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 {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Foreach Document
|
||||
* Call provided callback for each document in the collection
|
||||
*
|
||||
* @param string $projectId
|
||||
* @param string $collection
|
||||
* @param array $queries
|
||||
* @param callable $callback
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function foreachDocument(string $projectId, string $collection, array $queries, callable $callback): void
|
||||
{
|
||||
$limit = 50;
|
||||
$results = [];
|
||||
$sum = $limit;
|
||||
$latestDocument = null;
|
||||
$this->database->setNamespace('_' . $projectId);
|
||||
|
||||
while ($sum === $limit) {
|
||||
try {
|
||||
$results = $this->database->find($collection, $queries, $limit, cursor:$latestDocument);
|
||||
} catch (\Exception $e) {
|
||||
if (is_callable($this->errorHandler)) {
|
||||
call_user_func($this->errorHandler, $e, "fetch_documents_project_{$projectId}_collection_{$collection}");
|
||||
return;
|
||||
} else {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
if (empty($results)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$sum = count($results);
|
||||
|
||||
foreach ($results as $document) {
|
||||
if (is_callable($callback)) {
|
||||
$callback($document);
|
||||
}
|
||||
}
|
||||
$latestDocument = $results[array_key_last($results)];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sum
|
||||
* Calculate sum of a attribute of documents in collection
|
||||
*
|
||||
* @param string $projectId
|
||||
* @param string $collection
|
||||
* @param string $attribute
|
||||
* @param string $metric
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
private function sum(string $projectId, string $collection, string $attribute, string $metric): int
|
||||
{
|
||||
$this->database->setNamespace('_' . $projectId);
|
||||
|
||||
try {
|
||||
$sum = (int) $this->database->sum($collection, $attribute);
|
||||
$this->createOrUpdateMetric($projectId, $metric, $sum);
|
||||
return $sum;
|
||||
} catch (\Exception $e) {
|
||||
if (is_callable($this->errorHandler)) {
|
||||
call_user_func($this->errorHandler, $e, "fetch_sum_project_{$projectId}_collection_{$collection}");
|
||||
} else {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Count
|
||||
* Count number of documents in collection
|
||||
*
|
||||
* @param string $projectId
|
||||
* @param string $collection
|
||||
* @param string $metric
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
private function count(string $projectId, string $collection, string $metric): int
|
||||
{
|
||||
$this->database->setNamespace('_' . $projectId);
|
||||
|
||||
try {
|
||||
$count = $this->database->count($collection);
|
||||
$this->createOrUpdateMetric($projectId, $metric, $count);
|
||||
return $count;
|
||||
} catch (\Exception $e) {
|
||||
if (is_callable($this->errorHandler)) {
|
||||
call_user_func($this->errorHandler, $e, "fetch_count_project_{$projectId}_collection_{$collection}");
|
||||
} else {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deployments Total
|
||||
* Total sum of storage used by deployments
|
||||
*
|
||||
* @param string $projectId
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
private function deploymentsTotal(string $projectId): int
|
||||
{
|
||||
return $this->sum($projectId, 'deployments', 'size', 'stroage.deployments.total');
|
||||
}
|
||||
|
||||
/**
|
||||
* Users Stats
|
||||
* Metric: users.count
|
||||
*
|
||||
* @param string $projectId
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function usersStats(string $projectId): void
|
||||
{
|
||||
$this->count($projectId, 'users', 'users.count');
|
||||
}
|
||||
|
||||
/**
|
||||
* Storage Stats
|
||||
* Metrics: storage.total, storage.files.total, storage.buckets.{bucketId}.files.total,
|
||||
* storage.buckets.count, storage.files.count, storage.buckets.{bucketId}.files.count
|
||||
*
|
||||
* @param string $projectId
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function storageStats(string $projectId): void
|
||||
{
|
||||
$deploymentsTotal = $this->deploymentsTotal($projectId);
|
||||
|
||||
$projectFilesTotal = 0;
|
||||
$projectFilesCount = 0;
|
||||
|
||||
$metric = 'storage.buckets.count';
|
||||
$this->count($projectId, 'buckets', $metric);
|
||||
|
||||
$this->foreachDocument($projectId, 'buckets', [], function ($bucket) use (&$projectFilesCount, &$projectFilesTotal, $projectId,) {
|
||||
$metric = "storage.buckets.{$bucket->getId()}.files.count";
|
||||
|
||||
$count = $this->count($projectId, 'bucket_' . $bucket->getInternalId(), $metric);
|
||||
$projectFilesCount += $count;
|
||||
|
||||
$metric = "storage.buckets.{$bucket->getId()}.files.total";
|
||||
$sum = $this->sum($projectId, 'bucket_' . $bucket->getInternalId(), 'sizeOriginal', $metric);
|
||||
$projectFilesTotal += $sum;
|
||||
});
|
||||
|
||||
$this->createOrUpdateMetric($projectId, 'storage.files.count', $projectFilesCount);
|
||||
$this->createOrUpdateMetric($projectId, 'storage.files.total', $projectFilesTotal);
|
||||
|
||||
$this->createOrUpdateMetric($projectId, 'storage.total', $projectFilesTotal + $deploymentsTotal);
|
||||
}
|
||||
|
||||
/**
|
||||
* Database Stats
|
||||
* Collect all database stats
|
||||
* Metrics: database.collections.count, database.collections.{collectionId}.documents.count,
|
||||
* database.documents.count
|
||||
*
|
||||
* @param string $projectId
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function databaseStats(string $projectId): void
|
||||
{
|
||||
$projectDocumentsCount = 0;
|
||||
$projectCollectionsCount = 0;
|
||||
|
||||
$this->count($projectId, 'databases', 'databases.count');
|
||||
|
||||
$this->foreachDocument($projectId, 'databases', [], function ($database) use (&$projectDocumentsCount, &$projectCollectionsCount, $projectId) {
|
||||
$metric = "databases.{$database->getId()}.collections.count";
|
||||
$count = $this->count($projectId, 'database_' . $database->getInternalId(), $metric);
|
||||
$projectCollectionsCount += $count;
|
||||
$databaseDocumentsCount = 0;
|
||||
|
||||
$this->foreachDocument($projectId, 'database_' . $database->getInternalId(), [], function ($collection) use (&$projectDocumentsCount, &$databaseDocumentsCount, $projectId, $database) {
|
||||
$metric = "databases.{$database->getId()}.collections.{$collection->getId()}.documents.count";
|
||||
|
||||
$count = $this->count($projectId, 'database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $metric);
|
||||
$projectDocumentsCount += $count;
|
||||
$databaseDocumentsCount += $count;
|
||||
});
|
||||
|
||||
$this->createOrUpdateMetric($projectId, "databases.{$database->getId()}.documents.count", $databaseDocumentsCount);
|
||||
});
|
||||
|
||||
$this->createOrUpdateMetric($projectId, 'databases.collections.count', $projectCollectionsCount);
|
||||
$this->createOrUpdateMetric($projectId, 'databases.documents.count', $projectDocumentsCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect Stats
|
||||
* Collect all database related stats
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function collect(): void
|
||||
{
|
||||
$this->foreachDocument('console', 'projects', [], function (Document $project) {
|
||||
$projectId = $project->getInternalId();
|
||||
|
||||
$this->usersStats($projectId);
|
||||
$this->databaseStats($projectId);
|
||||
$this->storageStats($projectId);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -22,11 +22,11 @@ use Appwrite\Utopia\Response\Model\AttributeIP;
|
||||
use Appwrite\Utopia\Response\Model\AttributeURL;
|
||||
use Appwrite\Utopia\Response\Model\BaseList;
|
||||
use Appwrite\Utopia\Response\Model\Collection;
|
||||
use Appwrite\Utopia\Response\Model\Database;
|
||||
use Appwrite\Utopia\Response\Model\Continent;
|
||||
use Appwrite\Utopia\Response\Model\Country;
|
||||
use Appwrite\Utopia\Response\Model\Currency;
|
||||
use Appwrite\Utopia\Response\Model\Document as ModelDocument;
|
||||
use Appwrite\Utopia\Response\Model\DocumentList;
|
||||
use Appwrite\Utopia\Response\Model\Domain;
|
||||
use Appwrite\Utopia\Response\Model\Error;
|
||||
use Appwrite\Utopia\Response\Model\ErrorDev;
|
||||
@@ -65,6 +65,7 @@ use Appwrite\Utopia\Response\Model\Runtime;
|
||||
use Appwrite\Utopia\Response\Model\UsageBuckets;
|
||||
use Appwrite\Utopia\Response\Model\UsageCollection;
|
||||
use Appwrite\Utopia\Response\Model\UsageDatabase;
|
||||
use Appwrite\Utopia\Response\Model\UsageDatabases;
|
||||
use Appwrite\Utopia\Response\Model\UsageFunctions;
|
||||
use Appwrite\Utopia\Response\Model\UsageProject;
|
||||
use Appwrite\Utopia\Response\Model\UsageStorage;
|
||||
@@ -76,116 +77,119 @@ use Appwrite\Utopia\Response\Model\UsageUsers;
|
||||
class Response extends SwooleResponse
|
||||
{
|
||||
// General
|
||||
const MODEL_NONE = 'none';
|
||||
const MODEL_ANY = 'any';
|
||||
const MODEL_LOG = 'log';
|
||||
const MODEL_LOG_LIST = 'logList';
|
||||
const MODEL_ERROR = 'error';
|
||||
const MODEL_METRIC = 'metric';
|
||||
const MODEL_METRIC_LIST = 'metricList';
|
||||
const MODEL_ERROR_DEV = 'errorDev';
|
||||
const MODEL_BASE_LIST = 'baseList';
|
||||
const MODEL_USAGE_DATABASE = 'usageDatabase';
|
||||
const MODEL_USAGE_COLLECTION = 'usageCollection';
|
||||
const MODEL_USAGE_USERS = 'usageUsers';
|
||||
const MODEL_USAGE_BUCKETS = 'usageBuckets';
|
||||
const MODEL_USAGE_STORAGE = 'usageStorage';
|
||||
const MODEL_USAGE_FUNCTIONS = 'usageFunctions';
|
||||
const MODEL_USAGE_PROJECT = 'usageProject';
|
||||
public const MODEL_NONE = 'none';
|
||||
public const MODEL_ANY = 'any';
|
||||
public const MODEL_LOG = 'log';
|
||||
public const MODEL_LOG_LIST = 'logList';
|
||||
public const MODEL_ERROR = 'error';
|
||||
public const MODEL_METRIC = 'metric';
|
||||
public const MODEL_METRIC_LIST = 'metricList';
|
||||
public const MODEL_ERROR_DEV = 'errorDev';
|
||||
public const MODEL_BASE_LIST = 'baseList';
|
||||
public const MODEL_USAGE_DATABASES = 'usageDatabases';
|
||||
public const MODEL_USAGE_DATABASE = 'usageDatabase';
|
||||
public const MODEL_USAGE_COLLECTION = 'usageCollection';
|
||||
public const MODEL_USAGE_USERS = 'usageUsers';
|
||||
public const MODEL_USAGE_BUCKETS = 'usageBuckets';
|
||||
public const MODEL_USAGE_STORAGE = 'usageStorage';
|
||||
public const MODEL_USAGE_FUNCTIONS = 'usageFunctions';
|
||||
public const MODEL_USAGE_PROJECT = 'usageProject';
|
||||
|
||||
// Database
|
||||
const MODEL_COLLECTION = 'collection';
|
||||
const MODEL_COLLECTION_LIST = 'collectionList';
|
||||
const MODEL_INDEX = 'index';
|
||||
const MODEL_INDEX_LIST = 'indexList';
|
||||
const MODEL_DOCUMENT = 'document';
|
||||
const MODEL_DOCUMENT_LIST = 'documentList';
|
||||
public const MODEL_DATABASE = 'database';
|
||||
public const MODEL_DATABASE_LIST = 'databaseList';
|
||||
public const MODEL_COLLECTION = 'collection';
|
||||
public const MODEL_COLLECTION_LIST = 'collectionList';
|
||||
public const MODEL_INDEX = 'index';
|
||||
public const MODEL_INDEX_LIST = 'indexList';
|
||||
public const MODEL_DOCUMENT = 'document';
|
||||
public const MODEL_DOCUMENT_LIST = 'documentList';
|
||||
|
||||
// Database Attributes
|
||||
const MODEL_ATTRIBUTE = 'attribute';
|
||||
const MODEL_ATTRIBUTE_LIST = 'attributeList';
|
||||
const MODEL_ATTRIBUTE_STRING = 'attributeString';
|
||||
const MODEL_ATTRIBUTE_INTEGER = 'attributeInteger';
|
||||
const MODEL_ATTRIBUTE_FLOAT = 'attributeFloat';
|
||||
const MODEL_ATTRIBUTE_BOOLEAN = 'attributeBoolean';
|
||||
const MODEL_ATTRIBUTE_EMAIL = 'attributeEmail';
|
||||
const MODEL_ATTRIBUTE_ENUM = 'attributeEnum';
|
||||
const MODEL_ATTRIBUTE_IP = 'attributeIp';
|
||||
const MODEL_ATTRIBUTE_URL = 'attributeUrl';
|
||||
public const MODEL_ATTRIBUTE = 'attribute';
|
||||
public const MODEL_ATTRIBUTE_LIST = 'attributeList';
|
||||
public const MODEL_ATTRIBUTE_STRING = 'attributeString';
|
||||
public const MODEL_ATTRIBUTE_INTEGER = 'attributeInteger';
|
||||
public const MODEL_ATTRIBUTE_FLOAT = 'attributeFloat';
|
||||
public const MODEL_ATTRIBUTE_BOOLEAN = 'attributeBoolean';
|
||||
public const MODEL_ATTRIBUTE_EMAIL = 'attributeEmail';
|
||||
public const MODEL_ATTRIBUTE_ENUM = 'attributeEnum';
|
||||
public const MODEL_ATTRIBUTE_IP = 'attributeIp';
|
||||
public const MODEL_ATTRIBUTE_URL = 'attributeUrl';
|
||||
|
||||
// Users
|
||||
const MODEL_USER = 'user';
|
||||
const MODEL_USER_LIST = 'userList';
|
||||
const MODEL_SESSION = 'session';
|
||||
const MODEL_SESSION_LIST = 'sessionList';
|
||||
const MODEL_TOKEN = 'token';
|
||||
const MODEL_JWT = 'jwt';
|
||||
const MODEL_PREFERENCES = 'preferences';
|
||||
public const MODEL_USER = 'user';
|
||||
public const MODEL_USER_LIST = 'userList';
|
||||
public const MODEL_SESSION = 'session';
|
||||
public const MODEL_SESSION_LIST = 'sessionList';
|
||||
public const MODEL_TOKEN = 'token';
|
||||
public const MODEL_JWT = 'jwt';
|
||||
public const MODEL_PREFERENCES = 'preferences';
|
||||
|
||||
// Storage
|
||||
const MODEL_FILE = 'file';
|
||||
const MODEL_FILE_LIST = 'fileList';
|
||||
const MODEL_BUCKET = 'bucket';
|
||||
const MODEL_BUCKET_LIST = 'bucketList';
|
||||
public const MODEL_FILE = 'file';
|
||||
public const MODEL_FILE_LIST = 'fileList';
|
||||
public const MODEL_BUCKET = 'bucket';
|
||||
public const MODEL_BUCKET_LIST = 'bucketList';
|
||||
|
||||
// Locale
|
||||
const MODEL_LOCALE = 'locale';
|
||||
const MODEL_COUNTRY = 'country';
|
||||
const MODEL_COUNTRY_LIST = 'countryList';
|
||||
const MODEL_CONTINENT = 'continent';
|
||||
const MODEL_CONTINENT_LIST = 'continentList';
|
||||
const MODEL_CURRENCY = 'currency';
|
||||
const MODEL_CURRENCY_LIST = 'currencyList';
|
||||
const MODEL_LANGUAGE = 'language';
|
||||
const MODEL_LANGUAGE_LIST = 'languageList';
|
||||
const MODEL_PHONE = 'phone';
|
||||
const MODEL_PHONE_LIST = 'phoneList';
|
||||
public const MODEL_LOCALE = 'locale';
|
||||
public const MODEL_COUNTRY = 'country';
|
||||
public const MODEL_COUNTRY_LIST = 'countryList';
|
||||
public const MODEL_CONTINENT = 'continent';
|
||||
public const MODEL_CONTINENT_LIST = 'continentList';
|
||||
public const MODEL_CURRENCY = 'currency';
|
||||
public const MODEL_CURRENCY_LIST = 'currencyList';
|
||||
public const MODEL_LANGUAGE = 'language';
|
||||
public const MODEL_LANGUAGE_LIST = 'languageList';
|
||||
public const MODEL_PHONE = 'phone';
|
||||
public const MODEL_PHONE_LIST = 'phoneList';
|
||||
|
||||
// Teams
|
||||
const MODEL_TEAM = 'team';
|
||||
const MODEL_TEAM_LIST = 'teamList';
|
||||
const MODEL_MEMBERSHIP = 'membership';
|
||||
const MODEL_MEMBERSHIP_LIST = 'membershipList';
|
||||
public const MODEL_TEAM = 'team';
|
||||
public const MODEL_TEAM_LIST = 'teamList';
|
||||
public const MODEL_MEMBERSHIP = 'membership';
|
||||
public const MODEL_MEMBERSHIP_LIST = 'membershipList';
|
||||
|
||||
// Functions
|
||||
const MODEL_FUNCTION = 'function';
|
||||
const MODEL_FUNCTION_LIST = 'functionList';
|
||||
const MODEL_RUNTIME = 'runtime';
|
||||
const MODEL_RUNTIME_LIST = 'runtimeList';
|
||||
const MODEL_DEPLOYMENT = 'deployment';
|
||||
const MODEL_DEPLOYMENT_LIST = 'deploymentList';
|
||||
const MODEL_EXECUTION = 'execution';
|
||||
const MODEL_EXECUTION_LIST = 'executionList';
|
||||
const MODEL_BUILD = 'build';
|
||||
const MODEL_BUILD_LIST = 'buildList'; // Not used anywhere yet
|
||||
const MODEL_FUNC_PERMISSIONS = 'funcPermissions';
|
||||
public const MODEL_FUNCTION = 'function';
|
||||
public const MODEL_FUNCTION_LIST = 'functionList';
|
||||
public const MODEL_RUNTIME = 'runtime';
|
||||
public const MODEL_RUNTIME_LIST = 'runtimeList';
|
||||
public const MODEL_DEPLOYMENT = 'deployment';
|
||||
public const MODEL_DEPLOYMENT_LIST = 'deploymentList';
|
||||
public const MODEL_EXECUTION = 'execution';
|
||||
public const MODEL_EXECUTION_LIST = 'executionList';
|
||||
public const MODEL_BUILD = 'build';
|
||||
public const MODEL_BUILD_LIST = 'buildList'; // Not used anywhere yet
|
||||
public const MODEL_FUNC_PERMISSIONS = 'funcPermissions';
|
||||
|
||||
// Project
|
||||
const MODEL_PROJECT = 'project';
|
||||
const MODEL_PROJECT_LIST = 'projectList';
|
||||
const MODEL_WEBHOOK = 'webhook';
|
||||
const MODEL_WEBHOOK_LIST = 'webhookList';
|
||||
const MODEL_KEY = 'key';
|
||||
const MODEL_KEY_LIST = 'keyList';
|
||||
const MODEL_PLATFORM = 'platform';
|
||||
const MODEL_PLATFORM_LIST = 'platformList';
|
||||
const MODEL_DOMAIN = 'domain';
|
||||
const MODEL_DOMAIN_LIST = 'domainList';
|
||||
public const MODEL_PROJECT = 'project';
|
||||
public const MODEL_PROJECT_LIST = 'projectList';
|
||||
public const MODEL_WEBHOOK = 'webhook';
|
||||
public const MODEL_WEBHOOK_LIST = 'webhookList';
|
||||
public const MODEL_KEY = 'key';
|
||||
public const MODEL_KEY_LIST = 'keyList';
|
||||
public const MODEL_PLATFORM = 'platform';
|
||||
public const MODEL_PLATFORM_LIST = 'platformList';
|
||||
public const MODEL_DOMAIN = 'domain';
|
||||
public const MODEL_DOMAIN_LIST = 'domainList';
|
||||
|
||||
// Health
|
||||
const MODEL_HEALTH_STATUS = 'healthStatus';
|
||||
const MODEL_HEALTH_VERSION = 'healthVersion';
|
||||
const MODEL_HEALTH_QUEUE = 'healthQueue';
|
||||
const MODEL_HEALTH_TIME = 'healthTime';
|
||||
const MODEL_HEALTH_ANTIVIRUS = 'healthAntivirus';
|
||||
public const MODEL_HEALTH_STATUS = 'healthStatus';
|
||||
public const MODEL_HEALTH_VERSION = 'healthVersion';
|
||||
public const MODEL_HEALTH_QUEUE = 'healthQueue';
|
||||
public const MODEL_HEALTH_TIME = 'healthTime';
|
||||
public const MODEL_HEALTH_ANTIVIRUS = 'healthAntivirus';
|
||||
|
||||
// Deprecated
|
||||
const MODEL_PERMISSIONS = 'permissions';
|
||||
const MODEL_RULE = 'rule';
|
||||
const MODEL_TASK = 'task';
|
||||
public const MODEL_PERMISSIONS = 'permissions';
|
||||
public const MODEL_RULE = 'rule';
|
||||
public const MODEL_TASK = 'task';
|
||||
|
||||
// Tests (keep last)
|
||||
const MODEL_MOCK = 'mock';
|
||||
public const MODEL_MOCK = 'mock';
|
||||
|
||||
/**
|
||||
* @var Filter
|
||||
@@ -213,6 +217,7 @@ class Response extends SwooleResponse
|
||||
// Lists
|
||||
->setModel(new BaseList('Documents List', self::MODEL_DOCUMENT_LIST, 'documents', self::MODEL_DOCUMENT))
|
||||
->setModel(new BaseList('Collections List', self::MODEL_COLLECTION_LIST, 'collections', self::MODEL_COLLECTION))
|
||||
->setModel(new BaseList('Databases List', self::MODEL_DATABASE_LIST, 'databases', self::MODEL_DATABASE))
|
||||
->setModel(new BaseList('Indexes List', self::MODEL_INDEX_LIST, 'indexes', self::MODEL_INDEX))
|
||||
->setModel(new BaseList('Users List', self::MODEL_USER_LIST, 'users', self::MODEL_USER))
|
||||
->setModel(new BaseList('Sessions List', self::MODEL_SESSION_LIST, 'sessions', self::MODEL_SESSION))
|
||||
@@ -238,6 +243,7 @@ class Response extends SwooleResponse
|
||||
->setModel(new BaseList('Phones List', self::MODEL_PHONE_LIST, 'phones', self::MODEL_PHONE))
|
||||
->setModel(new BaseList('Metric List', self::MODEL_METRIC_LIST, 'metrics', self::MODEL_METRIC, true, false))
|
||||
// Entities
|
||||
->setModel(new Database())
|
||||
->setModel(new Collection())
|
||||
->setModel(new Attribute())
|
||||
->setModel(new AttributeList())
|
||||
@@ -283,6 +289,7 @@ class Response extends SwooleResponse
|
||||
->setModel(new HealthTime())
|
||||
->setModel(new HealthVersion())
|
||||
->setModel(new Metric())
|
||||
->setModel(new UsageDatabases())
|
||||
->setModel(new UsageDatabase())
|
||||
->setModel(new UsageCollection())
|
||||
->setModel(new UsageUsers())
|
||||
@@ -302,7 +309,7 @@ class Response extends SwooleResponse
|
||||
/**
|
||||
* HTTP content types
|
||||
*/
|
||||
const CONTENT_TYPE_YAML = 'application/x-yaml';
|
||||
public const CONTENT_TYPE_YAML = 'application/x-yaml';
|
||||
|
||||
/**
|
||||
* List of defined output objects
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Response\Filters;
|
||||
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Filter;
|
||||
|
||||
class V14 extends Filter
|
||||
{
|
||||
// Convert 0.15 Data format to 0.14 format
|
||||
public function parse(array $content, string $model): array
|
||||
{
|
||||
$parsedResponse = $content;
|
||||
|
||||
switch ($model) {
|
||||
case Response::MODEL_SESSION:
|
||||
case Response::MODEL_TOKEN:
|
||||
$parsedResponse = $this->parseRemoveAttributes($content, ['$createdAt']);
|
||||
|
||||
break;
|
||||
case Response::MODEL_SESSION_LIST:
|
||||
$parsedResponse = $this->parseRemoveAttributesList($content, 'domains', ['$createdAt']);
|
||||
|
||||
break;
|
||||
case Response::MODEL_DOCUMENT:
|
||||
case Response::MODEL_DOMAIN:
|
||||
case Response::MODEL_FUNCTION:
|
||||
case Response::MODEL_TEAM:
|
||||
case Response::MODEL_MEMBERSHIP:
|
||||
case Response::MODEL_PLATFORM:
|
||||
case Response::MODEL_PROJECT:
|
||||
case Response::MODEL_USER:
|
||||
case Response::MODEL_WEBHOOK:
|
||||
$parsedResponse = $this->parseRemoveAttributes($content, ['$createdAt', '$updatedAt']);
|
||||
|
||||
break;
|
||||
case Response::MODEL_DOCUMENT_LIST:
|
||||
$parsedResponse = $this->parseRemoveAttributesList($content, 'documents', ['$createdAt', '$updatedAt']);
|
||||
|
||||
break;
|
||||
case Response::MODEL_DOMAIN_LIST:
|
||||
$parsedResponse = $this->parseRemoveAttributesList($content, 'domains', ['$createdAt', '$updatedAt']);
|
||||
|
||||
break;
|
||||
case Response::MODEL_FUNCTION_LIST:
|
||||
$parsedResponse = $this->parseRemoveAttributesList($content, 'functions', ['$createdAt', '$updatedAt']);
|
||||
|
||||
break;
|
||||
case Response::MODEL_TEAM_LIST:
|
||||
$parsedResponse = $this->parseRemoveAttributesList($content, 'teams', ['$createdAt', '$updatedAt']);
|
||||
|
||||
break;
|
||||
case Response::MODEL_MEMBERSHIP_LIST:
|
||||
$parsedResponse = $this->parseRemoveAttributesList($content, 'memberships', ['$createdAt', '$updatedAt']);
|
||||
|
||||
break;
|
||||
case Response::MODEL_PLATFORM_LIST:
|
||||
$parsedResponse = $this->parseRemoveAttributesList($content, 'platforms', ['$createdAt', '$updatedAt']);
|
||||
|
||||
break;
|
||||
case Response::MODEL_PROJECT_LIST:
|
||||
$parsedResponse = $this->parseRemoveAttributesList($content, 'projects', ['$createdAt', '$updatedAt']);
|
||||
|
||||
break;
|
||||
case Response::MODEL_USER_LIST:
|
||||
$parsedResponse = $this->parseRemoveAttributesList($content, 'users', ['$createdAt', '$updatedAt']);
|
||||
|
||||
break;
|
||||
case Response::MODEL_WEBHOOK_LIST:
|
||||
$parsedResponse = $this->parseRemoveAttributesList($content, 'webhooks', ['$createdAt', '$updatedAt']);
|
||||
|
||||
break;
|
||||
case Response::MODEL_TEAM:
|
||||
case Response::MODEL_EXECUTION:
|
||||
case Response::MODEL_FILE:
|
||||
$parsedResponse = $this->parseCreatedAt($content);
|
||||
break;
|
||||
|
||||
case Response::MODEL_TEAM_LIST:
|
||||
$parsedResponse = $this->parseCreatedAtList($content, 'teams');
|
||||
break;
|
||||
|
||||
case Response::MODEL_EXECUTION_LIST:
|
||||
$parsedResponse = $this->parseCreatedAtList($content, 'executions');
|
||||
break;
|
||||
|
||||
case Response::MODEL_FILE_LIST:
|
||||
$parsedResponse = $this->parseCreatedAtList($content, 'files');
|
||||
break;
|
||||
|
||||
case Response::MODEL_FUNCTION:
|
||||
case Response::MODEL_DEPLOYMENT:
|
||||
case Response::MODEL_BUCKET:
|
||||
$parsedResponse = $this->parseCreatedAtAndUpdatedAt($content);
|
||||
break;
|
||||
|
||||
case Response::MODEL_FUNCTION_LIST:
|
||||
$parsedResponse = $this->parseCreatedAtAndUpdatedAtList($content, 'functions');
|
||||
break;
|
||||
|
||||
case Response::MODEL_DEPLOYMENT_LIST:
|
||||
$parsedResponse = $this->parseCreatedAtAndUpdatedAtList($content, 'deployments');
|
||||
break;
|
||||
|
||||
case Response::MODEL_BUCKET_LIST:
|
||||
$parsedResponse = $this->parseCreatedAtAndUpdatedAtList($content, 'buckets');
|
||||
break;
|
||||
}
|
||||
|
||||
return $parsedResponse;
|
||||
}
|
||||
|
||||
protected function parseRemoveAttributes(array $content, array $attributes)
|
||||
{
|
||||
foreach ($attributes as $attribute) {
|
||||
unset($content[$attribute]);
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
protected function parseRemoveAttributesList(array $content, string $property, array $attributes)
|
||||
{
|
||||
$documents = $content[$property];
|
||||
$parsedResponse = [];
|
||||
foreach ($documents as $document) {
|
||||
$parsedResponse[] = $this->parseRemoveAttributes($document, $attributes);
|
||||
}
|
||||
$content[$property] = $parsedResponse;
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
protected function parseCreatedAt(array $content)
|
||||
{
|
||||
$content['dateCreated'] = $content['$createdAt'];
|
||||
unset($content['$createdAt']);
|
||||
unset($content['$updatedAt']);
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
protected function parseCreatedAtList(array $content, string $property)
|
||||
{
|
||||
$documents = $content[$property];
|
||||
$parsedResponse = [];
|
||||
foreach ($documents as $document) {
|
||||
$parsedResponse[] = $this->parseCreatedAt($document);
|
||||
}
|
||||
$content[$property] = $parsedResponse;
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
protected function parseCreatedAtAndUpdatedAt(array $content)
|
||||
{
|
||||
$content['dateCreated'] = $content['$createdAt'];
|
||||
$content['dateUpdated'] = $content['$updatedAt'];
|
||||
unset($content['$createdAt']);
|
||||
unset($content['$updatedAt']);
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
protected function parseCreatedAtAndUpdatedAtList(array $content, string $property)
|
||||
{
|
||||
$documents = $content[$property];
|
||||
$parsedResponse = [];
|
||||
foreach ($documents as $document) {
|
||||
$parsedResponse[] = $this->parseCreatedAtAndUpdatedAt($document);
|
||||
}
|
||||
$content[$property] = $parsedResponse;
|
||||
|
||||
return $content;
|
||||
}
|
||||
}
|
||||
@@ -6,11 +6,11 @@ use Utopia\Database\Document;
|
||||
|
||||
abstract class Model
|
||||
{
|
||||
const TYPE_STRING = 'string';
|
||||
const TYPE_INTEGER = 'integer';
|
||||
const TYPE_FLOAT = 'double';
|
||||
const TYPE_BOOLEAN = 'boolean';
|
||||
const TYPE_JSON = 'json';
|
||||
public const TYPE_STRING = 'string';
|
||||
public const TYPE_INTEGER = 'integer';
|
||||
public const TYPE_FLOAT = 'double';
|
||||
public const TYPE_BOOLEAN = 'boolean';
|
||||
public const TYPE_JSON = 'json';
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
|
||||
@@ -16,6 +16,18 @@ class Bucket extends Model
|
||||
'default' => '',
|
||||
'example' => '5e5ea5c16897e',
|
||||
])
|
||||
->addRule('$createdAt', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Bucket creation date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
])
|
||||
->addRule('$updatedAt', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Bucket update date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
])
|
||||
->addRule('$read', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'File read permissions.',
|
||||
@@ -36,18 +48,6 @@ class Bucket extends Model
|
||||
'default' => '',
|
||||
'example' => 'file',
|
||||
])
|
||||
->addRule('dateCreated', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Bucket creation date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
])
|
||||
->addRule('dateUpdated', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Bucket update date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
])
|
||||
->addRule('name', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Bucket name.',
|
||||
|
||||
@@ -16,6 +16,18 @@ class Collection extends Model
|
||||
'default' => '',
|
||||
'example' => '5e5ea5c16897e',
|
||||
])
|
||||
->addRule('$createdAt', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Collection creation date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
])
|
||||
->addRule('$updatedAt', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Collection update date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
])
|
||||
->addRule('$read', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Collection read permissions.',
|
||||
@@ -30,6 +42,12 @@ class Collection extends Model
|
||||
'example' => 'user:608f9da25e7e1',
|
||||
'array' => true
|
||||
])
|
||||
->addRule('databaseId', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Database ID.',
|
||||
'default' => '',
|
||||
'example' => '5e5ea5c16897e',
|
||||
])
|
||||
->addRule('name', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Collection name.',
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Response\Model;
|
||||
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Model;
|
||||
|
||||
class Database extends Model
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->addRule('$id', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Database ID.',
|
||||
'default' => '',
|
||||
'example' => '5e5ea5c16897e',
|
||||
])
|
||||
->addRule('name', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Database name.',
|
||||
'default' => '',
|
||||
'example' => 'My Database',
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return 'Database';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return Response::MODEL_DATABASE;
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,18 @@ class Deployment extends Model
|
||||
'default' => '',
|
||||
'example' => '5e5ea5c16897e',
|
||||
])
|
||||
->addRule('$createdAt', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Deployment creation date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
])
|
||||
->addRule('$updatedAt', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Deployment update date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
])
|
||||
->addRule('resourceId', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Resource ID.',
|
||||
@@ -28,12 +40,6 @@ class Deployment extends Model
|
||||
'default' => '',
|
||||
'example' => 'functions',
|
||||
])
|
||||
->addRule('dateCreated', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'The deployment creation date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
])
|
||||
->addRule('entrypoint', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'The entrypoint file to use to execute the deployment code.',
|
||||
|
||||
@@ -42,6 +42,18 @@ class Document extends Any
|
||||
'default' => '',
|
||||
'example' => '5e5ea5c15117e',
|
||||
])
|
||||
->addRule('$createdAt', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Document creation date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
])
|
||||
->addRule('$updatedAt', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Document update date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
])
|
||||
->addRule('$read', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Document read permissions.',
|
||||
|
||||
@@ -21,6 +21,18 @@ class Domain extends Model
|
||||
'default' => '',
|
||||
'example' => '5e5ea5c16897e',
|
||||
])
|
||||
->addRule('$createdAt', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Domain creation date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
])
|
||||
->addRule('$updatedAt', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Domain update date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
])
|
||||
->addRule('domain', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Domain name.',
|
||||
|
||||
@@ -16,6 +16,18 @@ class Execution extends Model
|
||||
'default' => '',
|
||||
'example' => '5e5ea5c16897e',
|
||||
])
|
||||
->addRule('$createdAt', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Execution creation date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
])
|
||||
->addRule('$updatedAt', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Execution update date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
])
|
||||
->addRule('$read', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Execution read permissions.',
|
||||
@@ -29,12 +41,6 @@ class Execution extends Model
|
||||
'default' => '',
|
||||
'example' => '5e5ea6g16897e',
|
||||
])
|
||||
->addRule('dateCreated', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'The execution creation date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
])
|
||||
->addRule('trigger', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'The trigger that caused the function to execute. Possible values can be: `http`, `schedule`, or `event`.',
|
||||
|
||||
@@ -22,6 +22,18 @@ class File extends Model
|
||||
'default' => '',
|
||||
'example' => '5e5ea5c16897e',
|
||||
])
|
||||
->addRule('$createdAt', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'File creation date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
])
|
||||
->addRule('$updatedAt', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'File update date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
])
|
||||
->addRule('$read', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'File read permissions.',
|
||||
@@ -42,12 +54,6 @@ class File extends Model
|
||||
'default' => '',
|
||||
'example' => 'Pink.png',
|
||||
])
|
||||
->addRule('dateCreated', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'File creation date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
])
|
||||
->addRule('signature', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'File MD5 signature.',
|
||||
|
||||
@@ -18,6 +18,18 @@ class Func extends Model
|
||||
'default' => '',
|
||||
'example' => '5e5ea5c16897e',
|
||||
])
|
||||
->addRule('$createdAt', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Function creation date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
])
|
||||
->addRule('$updatedAt', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Function update date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
])
|
||||
->addRule('execute', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Execution permissions.',
|
||||
@@ -31,18 +43,6 @@ class Func extends Model
|
||||
'default' => '',
|
||||
'example' => 'My Function',
|
||||
])
|
||||
->addRule('dateCreated', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Function creation date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
])
|
||||
->addRule('dateUpdated', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Function update date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981257,
|
||||
])
|
||||
->addRule('status', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Function status. Possible values: `disabled`, `enabled`',
|
||||
|
||||
@@ -21,6 +21,18 @@ class Key extends Model
|
||||
'default' => '',
|
||||
'example' => '5e5ea5c16897e',
|
||||
])
|
||||
->addRule('$createdAt', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Key creation date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
])
|
||||
->addRule('$updatedAt', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Key update date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
])
|
||||
->addRule('name', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Key name.',
|
||||
|
||||
@@ -16,6 +16,18 @@ class Membership extends Model
|
||||
'default' => '',
|
||||
'example' => '5e5ea5c16897e',
|
||||
])
|
||||
->addRule('$createdAt', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Membership creation date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
])
|
||||
->addRule('$updatedAt', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Membership update date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
])
|
||||
->addRule('userId', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'User ID.',
|
||||
|
||||
@@ -21,6 +21,18 @@ class Platform extends Model
|
||||
'default' => '',
|
||||
'example' => '5e5ea5c16897e',
|
||||
])
|
||||
->addRule('$createdAt', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Project creation date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
])
|
||||
->addRule('$updatedAt', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Project update date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
])
|
||||
->addRule('name', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Platform name.',
|
||||
|
||||
@@ -23,6 +23,18 @@ class Project extends Model
|
||||
'default' => '',
|
||||
'example' => '5e5ea5c16897e',
|
||||
])
|
||||
->addRule('$createdAt', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Project creation date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
])
|
||||
->addRule('$updatedAt', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Project update date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
])
|
||||
->addRule('name', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Project name.',
|
||||
|
||||
@@ -16,6 +16,12 @@ class Session extends Model
|
||||
'default' => '',
|
||||
'example' => '5e5ea5c16897e',
|
||||
])
|
||||
->addRule('$createdAt', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Session creation date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
])
|
||||
->addRule('userId', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'User ID.',
|
||||
|
||||
@@ -16,18 +16,24 @@ class Team extends Model
|
||||
'default' => '',
|
||||
'example' => '5e5ea5c16897e',
|
||||
])
|
||||
->addRule('$createdAt', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Team creation date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
])
|
||||
->addRule('$updatedAt', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Team update date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
])
|
||||
->addRule('name', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Team name.',
|
||||
'default' => '',
|
||||
'example' => 'VIP',
|
||||
])
|
||||
->addRule('dateCreated', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Team creation date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
])
|
||||
->addRule('total', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Total number of team members.',
|
||||
|
||||
@@ -16,6 +16,12 @@ class Token extends Model
|
||||
'default' => '',
|
||||
'example' => 'bb8ea5c16897e',
|
||||
])
|
||||
->addRule('$createdAt', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Token creation date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
])
|
||||
->addRule('userId', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'User ID.',
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Response\Model;
|
||||
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Model;
|
||||
|
||||
class UsageDatabases extends Model
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->addRule('range', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'The time range of the usage stats.',
|
||||
'default' => '',
|
||||
'example' => '30d',
|
||||
])
|
||||
->addRule('databasesCount', [
|
||||
'type' => Response::MODEL_METRIC_LIST,
|
||||
'description' => 'Aggregated stats for total number of documents.',
|
||||
'default' => [],
|
||||
'example' => new \stdClass(),
|
||||
'array' => true
|
||||
])
|
||||
->addRule('documentsCount', [
|
||||
'type' => Response::MODEL_METRIC_LIST,
|
||||
'description' => 'Aggregated stats for total number of documents.',
|
||||
'default' => [],
|
||||
'example' => new \stdClass(),
|
||||
'array' => true
|
||||
])
|
||||
->addRule('collectionsCount', [
|
||||
'type' => Response::MODEL_METRIC_LIST,
|
||||
'description' => 'Aggregated stats for total number of collections.',
|
||||
'default' => [],
|
||||
'example' => new \stdClass(),
|
||||
'array' => true
|
||||
])
|
||||
->addRule('databasesCreate', [
|
||||
'type' => Response::MODEL_METRIC_LIST,
|
||||
'description' => 'Aggregated stats for documents created.',
|
||||
'default' => [],
|
||||
'example' => new \stdClass(),
|
||||
'array' => true
|
||||
])
|
||||
->addRule('databasesRead', [
|
||||
'type' => Response::MODEL_METRIC_LIST,
|
||||
'description' => 'Aggregated stats for documents read.',
|
||||
'default' => [],
|
||||
'example' => new \stdClass(),
|
||||
'array' => true
|
||||
])
|
||||
->addRule('databasesUpdate', [
|
||||
'type' => Response::MODEL_METRIC_LIST,
|
||||
'description' => 'Aggregated stats for documents updated.',
|
||||
'default' => [],
|
||||
'example' => new \stdClass(),
|
||||
'array' => true
|
||||
])
|
||||
->addRule('databasesDelete', [
|
||||
'type' => Response::MODEL_METRIC_LIST,
|
||||
'description' => 'Aggregated stats for total number of collections.',
|
||||
'default' => [],
|
||||
'example' => new \stdClass(),
|
||||
'array' => true
|
||||
])
|
||||
->addRule('documentsCreate', [
|
||||
'type' => Response::MODEL_METRIC_LIST,
|
||||
'description' => 'Aggregated stats for documents created.',
|
||||
'default' => [],
|
||||
'example' => new \stdClass(),
|
||||
'array' => true
|
||||
])
|
||||
->addRule('documentsRead', [
|
||||
'type' => Response::MODEL_METRIC_LIST,
|
||||
'description' => 'Aggregated stats for documents read.',
|
||||
'default' => [],
|
||||
'example' => new \stdClass(),
|
||||
'array' => true
|
||||
])
|
||||
->addRule('documentsUpdate', [
|
||||
'type' => Response::MODEL_METRIC_LIST,
|
||||
'description' => 'Aggregated stats for documents updated.',
|
||||
'default' => [],
|
||||
'example' => new \stdClass(),
|
||||
'array' => true
|
||||
])
|
||||
->addRule('documentsDelete', [
|
||||
'type' => Response::MODEL_METRIC_LIST,
|
||||
'description' => 'Aggregated stats for documents deleted.',
|
||||
'default' => [],
|
||||
'example' => new \stdClass(),
|
||||
'array' => true
|
||||
])
|
||||
->addRule('collectionsCreate', [
|
||||
'type' => Response::MODEL_METRIC_LIST,
|
||||
'description' => 'Aggregated stats for collections created.',
|
||||
'default' => [],
|
||||
'example' => new \stdClass(),
|
||||
'array' => true
|
||||
])
|
||||
->addRule('collectionsRead', [
|
||||
'type' => Response::MODEL_METRIC_LIST,
|
||||
'description' => 'Aggregated stats for collections read.',
|
||||
'default' => [],
|
||||
'example' => new \stdClass(),
|
||||
'array' => true
|
||||
])
|
||||
->addRule('collectionsUpdate', [
|
||||
'type' => Response::MODEL_METRIC_LIST,
|
||||
'description' => 'Aggregated stats for collections updated.',
|
||||
'default' => [],
|
||||
'example' => new \stdClass(),
|
||||
'array' => true
|
||||
])
|
||||
->addRule('collectionsDelete', [
|
||||
'type' => Response::MODEL_METRIC_LIST,
|
||||
'description' => 'Aggregated stats for collections delete.',
|
||||
'default' => [],
|
||||
'example' => new \stdClass(),
|
||||
'array' => true
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return 'UsageDatabases';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return Response::MODEL_USAGE_DATABASES;
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,18 @@ class User extends Model
|
||||
'default' => '',
|
||||
'example' => '5e5ea5c16897e',
|
||||
])
|
||||
->addRule('$createdAt', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'User creation date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
])
|
||||
->addRule('$updatedAt', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'User update date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
])
|
||||
->addRule('name', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'User name.',
|
||||
@@ -47,12 +59,24 @@ class User extends Model
|
||||
'default' => '',
|
||||
'example' => 'john@appwrite.io',
|
||||
])
|
||||
->addRule('phone', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'User phone number in E.164 format.',
|
||||
'default' => '',
|
||||
'example' => '+4930901820',
|
||||
])
|
||||
->addRule('emailVerification', [
|
||||
'type' => self::TYPE_BOOLEAN,
|
||||
'description' => 'Email verification status.',
|
||||
'default' => false,
|
||||
'example' => true,
|
||||
])
|
||||
->addRule('phoneVerification', [
|
||||
'type' => self::TYPE_BOOLEAN,
|
||||
'description' => 'Phone verification status.',
|
||||
'default' => false,
|
||||
'example' => true,
|
||||
])
|
||||
->addRule('prefs', [
|
||||
'type' => Response::MODEL_PREFERENCES,
|
||||
'description' => 'User preferences as a key-value object',
|
||||
|
||||
@@ -21,6 +21,18 @@ class Webhook extends Model
|
||||
'default' => '',
|
||||
'example' => '5e5ea5c16897e',
|
||||
])
|
||||
->addRule('$createdAt', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Webhook creation date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
])
|
||||
->addRule('$updatedAt', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Webhook update date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
])
|
||||
->addRule('name', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Webhook name.',
|
||||
@@ -58,6 +70,12 @@ class Webhook extends Model
|
||||
'default' => '',
|
||||
'example' => 'password',
|
||||
])
|
||||
->addRule('signatureKey', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Signature key which can be used to validated incoming',
|
||||
'default' => '',
|
||||
'example' => 'ad3d581ca230e2b7059c545e5a',
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,15 +8,15 @@ use Utopia\CLI\Console;
|
||||
|
||||
class Executor
|
||||
{
|
||||
const METHOD_GET = 'GET';
|
||||
const METHOD_POST = 'POST';
|
||||
const METHOD_PUT = 'PUT';
|
||||
const METHOD_PATCH = 'PATCH';
|
||||
const METHOD_DELETE = 'DELETE';
|
||||
const METHOD_HEAD = 'HEAD';
|
||||
const METHOD_OPTIONS = 'OPTIONS';
|
||||
const METHOD_CONNECT = 'CONNECT';
|
||||
const METHOD_TRACE = 'TRACE';
|
||||
public const METHOD_GET = 'GET';
|
||||
public const METHOD_POST = 'POST';
|
||||
public const METHOD_PUT = 'PUT';
|
||||
public const METHOD_PATCH = 'PATCH';
|
||||
public const METHOD_DELETE = 'DELETE';
|
||||
public const METHOD_HEAD = 'HEAD';
|
||||
public const METHOD_OPTIONS = 'OPTIONS';
|
||||
public const METHOD_CONNECT = 'CONNECT';
|
||||
public const METHOD_TRACE = 'TRACE';
|
||||
|
||||
private $endpoint;
|
||||
|
||||
@@ -186,10 +186,18 @@ class Executor
|
||||
);
|
||||
$response = $this->call(self::METHOD_POST, $route, $headers, $params, true, $requestTimeout);
|
||||
$status = $response['headers']['status-code'];
|
||||
|
||||
if ($status < 400) {
|
||||
return $response['body'];
|
||||
}
|
||||
break;
|
||||
case $status === 406:
|
||||
$response = $this->call(self::METHOD_POST, $route, $headers, $params, true, $requestTimeout);
|
||||
$status = $response['headers']['status-code'];
|
||||
|
||||
if ($status < 400) {
|
||||
return $response['body'];
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new \Exception($response['body']['message'], $status);
|
||||
|
||||
Reference in New Issue
Block a user